JAMES-2293 WebAdmin should allow to remove all mails from a mail repository
A task would be generated as the action can potentially be long. Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/f2d8f04e Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/f2d8f04e Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/f2d8f04e Branch: refs/heads/master Commit: f2d8f04ebd9cf7600466f054aaee24dec30198be Parents: 3c2559c Author: benwa <[email protected]> Authored: Mon Jan 22 13:46:58 2018 +0700 Committer: benwa <[email protected]> Committed: Thu Jan 25 11:39:06 2018 +0700 ---------------------------------------------------------------------- .../webadmin/routes/MailRepositoriesRoutes.java | 68 +++++++++++- .../service/ClearMailRepositoryTask.java | 97 +++++++++++++++++ .../service/MailRepositoryStoreService.java | 5 + .../routes/MailRepositoriesRoutesTest.java | 109 ++++++++++++++++++- 4 files changed, 275 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/f2d8f04e/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/routes/MailRepositoriesRoutes.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/routes/MailRepositoriesRoutes.java b/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/routes/MailRepositoriesRoutes.java index 4f106b7..5879de7 100644 --- a/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/routes/MailRepositoriesRoutes.java +++ b/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/routes/MailRepositoriesRoutes.java @@ -32,11 +32,15 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import org.apache.james.mailrepository.api.MailRepositoryStore; +import org.apache.james.task.Task; +import org.apache.james.task.TaskId; +import org.apache.james.task.TaskManager; import org.apache.james.util.streams.Limit; import org.apache.james.util.streams.Offset; import org.apache.james.webadmin.Constants; import org.apache.james.webadmin.Routes; import org.apache.james.webadmin.dto.ExtendedMailRepositoryResponse; +import org.apache.james.webadmin.dto.TaskIdDto; import org.apache.james.webadmin.service.MailRepositoryStoreService; import org.apache.james.webadmin.utils.ErrorResponder; import org.apache.james.webadmin.utils.ErrorResponder.ErrorType; @@ -51,6 +55,7 @@ import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; +import io.swagger.jaxrs.PATCH; import spark.Request; import spark.Service; @@ -63,12 +68,14 @@ public class MailRepositoriesRoutes implements Routes { private final JsonTransformer jsonTransformer; private final MailRepositoryStoreService repositoryStoreService; + private final TaskManager taskManager; private Service service; @Inject - public MailRepositoriesRoutes(MailRepositoryStoreService repositoryStoreService, JsonTransformer jsonTransformer) { + public MailRepositoriesRoutes(MailRepositoryStoreService repositoryStoreService, JsonTransformer jsonTransformer, TaskManager taskManager) { this.repositoryStoreService = repositoryStoreService; this.jsonTransformer = jsonTransformer; + this.taskManager = taskManager; } @Override @@ -84,6 +91,8 @@ public class MailRepositoriesRoutes implements Routes { defineGetMail(); defineDeleteMail(); + + defineDeleteAll(); } @GET @@ -169,7 +178,7 @@ public class MailRepositoriesRoutes implements Routes { .type(ErrorResponder.ErrorType.NOT_FOUND) .message("Could not retrieve " + mailKey) .haltError()); - } catch (MailRepositoryStore.MailRepositoryStoreException| MessagingException e) { + } catch (MailRepositoryStore.MailRepositoryStoreException | MessagingException e) { throw ErrorResponder.builder() .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500) .type(ErrorResponder.ErrorType.SERVER_ERROR) @@ -237,7 +246,60 @@ public class MailRepositoriesRoutes implements Routes { }); } -private Optional<Integer> assertPositiveInteger(Request request, String parameterName) { + @PATCH + @Path("/{encodedUrl}/mails") + @ApiOperation(value = "Deleting all mails in that mailRepository") + @ApiImplicitParams({ + @ApiImplicitParam( + required = true, + paramType = "query parameter", + dataType = "String", + example = "?action=clear", + value = "Specify the action. Only clear is supported. clear removes all mails from that mail repository.") + }) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.CREATED_201, message = "All mails are deleted", response = TaskIdDto.class), + @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side."), + @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Bad request - unknown action") + }) + public void defineDeleteAll() { + service.patch(MAIL_REPOSITORIES + "/:encodedUrl/mails", (request, response) -> { + String url = URLDecoder.decode(request.params("encodedUrl"), StandardCharsets.UTF_8.displayName()); + try { + ensureSpecifiedClearAction(request); + Task task = repositoryStoreService.createClearMailRepositoryTask(url); + TaskId taskId = taskManager.submit(task); + return TaskIdDto.respond(response, taskId); + } catch (MailRepositoryStore.MailRepositoryStoreException | MessagingException e) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.INTERNAL_SERVER_ERROR_500) + .type(ErrorResponder.ErrorType.SERVER_ERROR) + .cause(e) + .message("Error while deleting all mails") + .haltError(); + } + }, jsonTransformer); + } + + private void ensureSpecifiedClearAction(Request request) { + String action = request.queryParams("action"); + if (Strings.isNullOrEmpty(action)) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .message("You need to specify an action. Currently only clear is supported.") + .haltError(); + } + if (!action.equals("clear")) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .message("Unknown action " + action) + .haltError(); + } + } + + private Optional<Integer> assertPositiveInteger(Request request, String parameterName) { try { return Optional.ofNullable(request.queryParams(parameterName)) .filter(s -> !Strings.isNullOrEmpty(s)) http://git-wip-us.apache.org/repos/asf/james-project/blob/f2d8f04e/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/service/ClearMailRepositoryTask.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/service/ClearMailRepositoryTask.java b/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/service/ClearMailRepositoryTask.java new file mode 100644 index 0000000..132e36c --- /dev/null +++ b/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/service/ClearMailRepositoryTask.java @@ -0,0 +1,97 @@ +/**************************************************************** + * 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 java.util.Optional; +import java.util.function.Supplier; + +import javax.mail.MessagingException; + +import org.apache.james.mailrepository.api.MailRepository; +import org.apache.james.task.Task; +import org.apache.james.task.TaskExecutionDetails; + +import com.google.common.base.Throwables; + +public class ClearMailRepositoryTask implements Task { + + public static final String TYPE = "clearMailRepository"; + + public static class AdditionalInformation implements TaskExecutionDetails.AdditionalInformation { + private final String repositoryUrl; + private final Supplier<Long> countSupplier; + private final long initialCount; + + public AdditionalInformation(String repositoryUrl, Supplier<Long> countSupplier) { + this.repositoryUrl = repositoryUrl; + this.initialCount = countSupplier.get(); + this.countSupplier = countSupplier; + } + + public String getRepositoryUrl() { + return repositoryUrl; + } + + public long getRemainingCount() { + return countSupplier.get(); + } + + public long getInitialCount() { + return initialCount; + } + } + + private final MailRepository mailRepository; + private final AdditionalInformation additionalInformation; + + public ClearMailRepositoryTask(MailRepository mailRepository, String url) { + this.mailRepository = mailRepository; + this.additionalInformation = new AdditionalInformation(url, this::getRemainingSize); + } + + @Override + public Result run() { + try { + mailRepository.removeAll(); + return Result.COMPLETED; + } catch (MessagingException e) { + LOGGER.error("Encountered error while clearing repository", e); + return Result.PARTIAL; + } + } + + @Override + public String type() { + return TYPE; + } + + @Override + public Optional<TaskExecutionDetails.AdditionalInformation> details() { + return Optional.of(additionalInformation); + } + + public long getRemainingSize() { + try { + return mailRepository.size(); + } catch (MessagingException e) { + throw Throwables.propagate(e); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/f2d8f04e/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/service/MailRepositoryStoreService.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/service/MailRepositoryStoreService.java b/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/service/MailRepositoryStoreService.java index d7ca74a..fd17507 100644 --- a/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/service/MailRepositoryStoreService.java +++ b/server/protocols/webadmin/webadmin-mailrepository/src/main/java/org/apache/james/webadmin/service/MailRepositoryStoreService.java @@ -27,6 +27,7 @@ import javax.mail.MessagingException; import org.apache.james.mailrepository.api.MailRepository; import org.apache.james.mailrepository.api.MailRepositoryStore; +import org.apache.james.task.Task; import org.apache.james.util.streams.Iterators; import org.apache.james.util.streams.Limit; import org.apache.james.util.streams.Offset; @@ -85,4 +86,8 @@ public class MailRepositoryStoreService { .remove(mailKey); } + public Task createClearMailRepositoryTask(String url) throws MailRepositoryStore.MailRepositoryStoreException, MessagingException { + return new ClearMailRepositoryTask(mailRepositoryStore.select(url), url); + } + } http://git-wip-us.apache.org/repos/asf/james-project/blob/f2d8f04e/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/routes/MailRepositoriesRoutesTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/routes/MailRepositoriesRoutesTest.java b/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/routes/MailRepositoriesRoutesTest.java index 6a3f0ac..6829f22 100644 --- a/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/routes/MailRepositoriesRoutesTest.java +++ b/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/routes/MailRepositoriesRoutesTest.java @@ -21,6 +21,7 @@ package org.apache.james.webadmin.routes; import static com.jayway.restassured.RestAssured.given; import static com.jayway.restassured.RestAssured.when; +import static com.jayway.restassured.RestAssured.with; import static com.jayway.restassured.config.EncoderConfig.encoderConfig; import static com.jayway.restassured.config.RestAssuredConfig.newConfig; import static org.apache.james.webadmin.WebAdminServer.NO_CONFIGURATION; @@ -32,6 +33,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.isEmptyOrNullString; +import static org.hamcrest.Matchers.notNullValue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -41,8 +43,10 @@ import java.util.List; import org.apache.james.mailrepository.api.MailRepositoryStore; import org.apache.james.mailrepository.memory.MemoryMailRepository; import org.apache.james.metrics.api.NoopMetricFactory; +import org.apache.james.task.MemoryTaskManager; import org.apache.james.webadmin.WebAdminServer; import org.apache.james.webadmin.WebAdminUtils; +import org.apache.james.webadmin.service.ClearMailRepositoryTask; import org.apache.james.webadmin.service.MailRepositoryStoreService; import org.apache.james.webadmin.utils.ErrorResponder; import org.apache.james.webadmin.utils.JsonTransformer; @@ -71,9 +75,13 @@ public class MailRepositoriesRoutesTest { mailRepositoryStore = mock(MailRepositoryStore.class); mailRepository = new MemoryMailRepository(); + MemoryTaskManager taskManager = new MemoryTaskManager(); + JsonTransformer jsonTransformer = new JsonTransformer(); webAdminServer = WebAdminUtils.createWebAdminServer( new NoopMetricFactory(), - new MailRepositoriesRoutes(new MailRepositoryStoreService(mailRepositoryStore), new JsonTransformer())); + new MailRepositoriesRoutes(new MailRepositoryStoreService(mailRepositoryStore), + jsonTransformer, taskManager), + new TasksRoutes(taskManager, jsonTransformer)); webAdminServer.configure(NO_CONFIGURATION); webAdminServer.await(); @@ -472,4 +480,103 @@ public class MailRepositoriesRoutesTest { .statusCode(HttpStatus.NO_CONTENT_204); } + @Test + public void deletingAllMailsShouldCreateATask() throws Exception { + when(mailRepositoryStore.select(URL_MY_REPO)).thenReturn(mailRepository); + + when() + .patch(URL_ESCAPED_MY_REPO + "/mails?action=clear") + .then() + .statusCode(HttpStatus.CREATED_201) + .header("Location", is(notNullValue())) + .body("taskId", is(notNullValue())); + } + + @Test + public void patchShouldOnlySupportClear() throws Exception { + when(mailRepositoryStore.select(URL_MY_REPO)).thenReturn(mailRepository); + + when() + .patch(URL_ESCAPED_MY_REPO + "/mails?action=invalid") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("Unknown action invalid")); + } + + @Test + public void patchShouldRequireAnAction() throws Exception { + when(mailRepositoryStore.select(URL_MY_REPO)).thenReturn(mailRepository); + + when() + .patch(URL_ESCAPED_MY_REPO + "/mails") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400) + .body("statusCode", is(400)) + .body("type", is(ErrorResponder.ErrorType.INVALID_ARGUMENT.getType())) + .body("message", is("You need to specify an action. Currently only clear is supported.")); + } + + @Test + public void clearTaskShouldHaveDetails() throws Exception { + when(mailRepositoryStore.select(URL_MY_REPO)).thenReturn(mailRepository); + + String name1 = "name1"; + String name2 = "name2"; + mailRepository.store(FakeMail.builder() + .name(name1) + .build()); + mailRepository.store(FakeMail.builder() + .name(name2) + .build()); + + String taskId = with() + .patch(URL_ESCAPED_MY_REPO + "/mails?action=clear") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .when() + .get(taskId + "/await") + .then() + .body("status", is("completed")) + .body("taskId", is(notNullValue())) + .body("type", is(ClearMailRepositoryTask.TYPE)) + .body("additionalInformation.repositoryUrl", is(URL_MY_REPO)) + .body("additionalInformation.initialCount", is(2)) + .body("additionalInformation.remainingCount", is(0)) + .body("startedDate", is(notNullValue())) + .body("submitDate", is(notNullValue())) + .body("completedDate", is(notNullValue())); + } + + @Test + public void clearTaskShouldRemoveAllTheMailsFromTheMailRepository() throws Exception { + when(mailRepositoryStore.select(URL_MY_REPO)).thenReturn(mailRepository); + + mailRepository.store(FakeMail.builder() + .name("name1") + .build()); + mailRepository.store(FakeMail.builder() + .name("name2") + .build()); + + String taskId = with() + .patch(URL_ESCAPED_MY_REPO + "/mails?action=clear") + .jsonPath() + .get("taskId"); + + given() + .basePath(TasksRoutes.BASE) + .get(taskId + "/await"); + + when() + .get(URL_ESCAPED_MY_REPO + "/mails") + .then() + .statusCode(HttpStatus.OK_200) + .body("", hasSize(0)); + } + } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
