JAMES-2292 implement PATCH /mailqueues/x/mails to flush mailqueue
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/cae93f35 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/cae93f35 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/cae93f35 Branch: refs/heads/master Commit: cae93f359e0c94801dab46c2e21f1633acd9cc44 Parents: cec918f Author: Matthieu Baechler <[email protected]> Authored: Wed Jan 24 15:00:07 2018 +0100 Committer: benwa <[email protected]> Committed: Fri Jan 26 08:32:40 2018 +0700 ---------------------------------------------------------------------- .../james/webadmin/dto/ForceDelivery.java | 39 +++++ .../james/webadmin/routes/MailQueueRoutes.java | 109 +++++++++++- .../webadmin/routes/MailQueueRoutesTest.java | 167 +++++++++++++++++++ .../queue/memory/MemoryMailQueueFactory.java | 3 +- 4 files changed, 307 insertions(+), 11 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/cae93f35/server/protocols/webadmin/webadmin-mailqueue/src/main/java/org/apache/james/webadmin/dto/ForceDelivery.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-mailqueue/src/main/java/org/apache/james/webadmin/dto/ForceDelivery.java b/server/protocols/webadmin/webadmin-mailqueue/src/main/java/org/apache/james/webadmin/dto/ForceDelivery.java new file mode 100644 index 0000000..98ade6a --- /dev/null +++ b/server/protocols/webadmin/webadmin-mailqueue/src/main/java/org/apache/james/webadmin/dto/ForceDelivery.java @@ -0,0 +1,39 @@ +/**************************************************************** + * 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.dto; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ForceDelivery { + + private final Optional<Boolean> delayed; + + @JsonCreator + public ForceDelivery(@JsonProperty("delayed") Optional<Boolean> delayed) { + this.delayed = delayed; + } + + public Optional<Boolean> getDelayed() { + return delayed; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/cae93f35/server/protocols/webadmin/webadmin-mailqueue/src/main/java/org/apache/james/webadmin/routes/MailQueueRoutes.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-mailqueue/src/main/java/org/apache/james/webadmin/routes/MailQueueRoutes.java b/server/protocols/webadmin/webadmin-mailqueue/src/main/java/org/apache/james/webadmin/routes/MailQueueRoutes.java index a20e4c4..6815258 100644 --- a/server/protocols/webadmin/webadmin-mailqueue/src/main/java/org/apache/james/webadmin/routes/MailQueueRoutes.java +++ b/server/protocols/webadmin/webadmin-mailqueue/src/main/java/org/apache/james/webadmin/routes/MailQueueRoutes.java @@ -40,10 +40,13 @@ import org.apache.james.util.streams.Iterators; import org.apache.james.util.streams.Limit; import org.apache.james.webadmin.Constants; import org.apache.james.webadmin.Routes; +import org.apache.james.webadmin.dto.ForceDelivery; import org.apache.james.webadmin.dto.MailQueueDTO; import org.apache.james.webadmin.dto.MailQueueItemDTO; import org.apache.james.webadmin.utils.ErrorResponder; import org.apache.james.webadmin.utils.ErrorResponder.ErrorType; +import org.apache.james.webadmin.utils.JsonExtractException; +import org.apache.james.webadmin.utils.JsonExtractor; import org.apache.james.webadmin.utils.JsonTransformer; import org.eclipse.jetty.http.HttpStatus; @@ -58,6 +61,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.HaltException; import spark.Request; import spark.Response; @@ -76,19 +80,20 @@ public class MailQueueRoutes implements Routes { private static final String DELAYED_QUERY_PARAM = "delayed"; private static final String LIMIT_QUERY_PARAM = "limit"; @VisibleForTesting static final int DEFAULT_LIMIT_VALUE = 100; - private static final String SENDER_QUERY_PARAM = "sender"; private static final String NAME_QUERY_PARAM = "name"; private static final String RECIPIENT_QUERY_PARAM = "recipient"; private final MailQueueFactory<ManageableMailQueue> mailQueueFactory; private final JsonTransformer jsonTransformer; + private final JsonExtractor<ForceDelivery> jsonExtractor; @Inject @SuppressWarnings("unchecked") @VisibleForTesting MailQueueRoutes(MailQueueFactory<?> mailQueueFactory, JsonTransformer jsonTransformer) { this.mailQueueFactory = (MailQueueFactory<ManageableMailQueue>) mailQueueFactory; this.jsonTransformer = jsonTransformer; + this.jsonExtractor = new JsonExtractor<>(ForceDelivery.class); } @Override @@ -100,6 +105,8 @@ public class MailQueueRoutes implements Routes { listMails(service); deleteMails(service); + + forceDelayedMailsDelivery(service); } @GET @@ -170,16 +177,16 @@ public class MailQueueRoutes implements Routes { @ApiImplicitParams({ @ApiImplicitParam(required = true, dataType = "string", name = "mailQueueName", paramType = "path"), @ApiImplicitParam( - required = false, - dataType = "boolean", - name = DELAYED_QUERY_PARAM, + required = false, + dataType = "boolean", + name = DELAYED_QUERY_PARAM, paramType = "query", example = "?delayed=true", value = "Whether the mails are delayed in the mail queue or not (already sent)."), @ApiImplicitParam( - required = false, - dataType = "int", - name = LIMIT_QUERY_PARAM, + required = false, + dataType = "int", + name = LIMIT_QUERY_PARAM, paramType = "query", example = "?limit=100", defaultValue = "100", @@ -195,8 +202,8 @@ public class MailQueueRoutes implements Routes { @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.") }) public void listMails(Service service) { - service.get(BASE_URL + SEPARATOR + MAIL_QUEUE_NAME + MAILS, - (request, response) -> listMails(request), + service.get(BASE_URL + SEPARATOR + MAIL_QUEUE_NAME + MAILS, + (request, response) -> listMails(request), jsonTransformer); } @@ -344,6 +351,79 @@ public class MailQueueRoutes implements Routes { } } + @PATCH + @Path("/{mailQueueName}/mails") + @ApiImplicitParams({ + @ApiImplicitParam( + required = true, + dataType = "string", + name = "mailQueueName", + paramType = "path"), + @ApiImplicitParam( + required = false, + dataType = "boolean", + name = DELAYED_QUERY_PARAM, + paramType = "query", + example = "?delayed=true", + value = "Whether the mails are delayed in the mail queue or not (already sent).") + }) + @ApiOperation( + value = "Force delayed mails delivery" + ) + @ApiResponses(value = { + @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "OK"), + @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Invalid request for getting the mail queue."), + @ApiResponse(code = HttpStatus.NOT_FOUND_404, message = "The MailQueue does not exist."), + @ApiResponse(code = HttpStatus.INTERNAL_SERVER_ERROR_500, message = "Internal server error - Something went bad on the server side.") + }) + public void forceDelayedMailsDelivery(Service service) { + service.patch(BASE_URL + SEPARATOR + MAIL_QUEUE_NAME + MAILS, + this::forceDelayedMailsDelivery, + jsonTransformer); + } + + private Response forceDelayedMailsDelivery(Request request, Response response) throws JsonExtractException, MailQueueException { + assertDelayedParamIsTrue(request); + assertPayloadContainsDelayedEntry(request); + ManageableMailQueue mailQueue = assertMailQueueExists(request); + + mailQueue.flush(); + + response.status(HttpStatus.NO_CONTENT_204); + return response; + } + + private ManageableMailQueue assertMailQueueExists(Request request) { + String mailQueueName = request.params(MAIL_QUEUE_NAME); + return mailQueueFactory.getQueue(mailQueueName) + .orElseThrow(() -> ErrorResponder.builder() + .message(String.format("%s can not be found", mailQueueName)) + .statusCode(HttpStatus.NOT_FOUND_404) + .type(ErrorType.NOT_FOUND) + .haltError()); + } + + private void assertPayloadContainsDelayedEntry(Request request) { + try { + if (jsonExtractor.parse(request.body()) + .getDelayed() + .orElse(true)) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorType.INVALID_ARGUMENT) + .message("This request requires payload to contain delayed attribute set to false") + .haltError(); + } + } catch (JsonExtractException e) { + throw ErrorResponder.builder() + .type(ErrorResponder.ErrorType.INVALID_ARGUMENT) + .statusCode(HttpStatus.BAD_REQUEST_400) + .message("Invalid JSON document: " + e.getMessage()) + .cause(e) + .haltError(); + } + } + private Object deleteMails(ManageableMailQueue queue, Optional<MailAddress> maybeSender, Optional<String> maybeName, Optional<MailAddress> maybeRecipient) { if (Booleans.countTrue(maybeSender.isPresent(), maybeName.isPresent(), maybeRecipient.isPresent()) != 1) { throw ErrorResponder.builder() @@ -358,4 +438,15 @@ public class MailQueueRoutes implements Routes { maybeRecipient.ifPresent(Throwing.consumer((MailAddress recipient) -> queue.remove(Type.Recipient, recipient.asString())).sneakyThrow()); return Constants.EMPTY_BODY; } + + private void assertDelayedParamIsTrue(Request request) { + if (!isDelayed(request.queryParams(DELAYED_QUERY_PARAM)).orElse(false)) { + throw ErrorResponder.builder() + .statusCode(HttpStatus.BAD_REQUEST_400) + .type(ErrorType.INVALID_ARGUMENT) + .message("This request requires delayed param to be set to true") + .haltError(); + } + } + } http://git-wip-us.apache.org/repos/asf/james-project/blob/cae93f35/server/protocols/webadmin/webadmin-mailqueue/src/test/java/org/apache/james/webadmin/routes/MailQueueRoutesTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/webadmin/webadmin-mailqueue/src/test/java/org/apache/james/webadmin/routes/MailQueueRoutesTest.java b/server/protocols/webadmin/webadmin-mailqueue/src/test/java/org/apache/james/webadmin/routes/MailQueueRoutesTest.java index 0279bea..f0f99d6 100644 --- a/server/protocols/webadmin/webadmin-mailqueue/src/test/java/org/apache/james/webadmin/routes/MailQueueRoutesTest.java +++ b/server/protocols/webadmin/webadmin-mailqueue/src/test/java/org/apache/james/webadmin/routes/MailQueueRoutesTest.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; @@ -30,6 +31,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import java.nio.charset.StandardCharsets; +import java.time.ZonedDateTime; import java.util.List; import java.util.concurrent.TimeUnit; @@ -399,6 +401,80 @@ public class MailQueueRoutesTest { } @Test + public void forcingDelayedMailsDeliveryShouldReturnNoContent() throws Exception { + MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); + + given() + .queryParam("delayed", "true") + .body("{\"delayed\": \"false\"}") + .when() + .patch(FIRST_QUEUE + "/mails") + .then() + .statusCode(HttpStatus.NO_CONTENT_204); + } + + @Test + public void forcingDelayedMailsDeliveryForUnknownQueueShouldReturnNotFound() throws Exception { + given() + .queryParam("delayed", "true") + .body("{\"delayed\": \"false\"}") + .when() + .patch("unknown queue" + "/mails") + .then() + .statusCode(HttpStatus.NOT_FOUND_404); + } + + @Test + public void forcingDelayedMailsDeliveryRequiresDelayedParameter() throws Exception { + MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); + + given() + .body("{\"delayed\": \"false\"}") + .when() + .patch(FIRST_QUEUE + "/mails") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void forcingDelayedMailsDeliveryShouldRejectFalseDelayedParam() throws Exception { + MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); + + given() + .queryParam("delayed", "false") + .body("{\"delayed\": \"false\"}") + .when() + .patch(FIRST_QUEUE + "/mails") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void forcingDelayedMailsDeliveryShouldRejectNonBooleanDelayedParam() throws Exception { + MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); + + given() + .queryParam("delayed", "wrong") + .body("{\"delayed\": \"false\"}") + .when() + .patch(FIRST_QUEUE + "/mails") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void forcingDelayedMailsDeliveryShouldRejectRequestWithoutBody() throws Exception { + MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); + + given() + .queryParam("delayed", "true") + .when() + .patch(FIRST_QUEUE + "/mails") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test public void deleteMailsShouldDeleteMailsWhenSenderIsGiven() throws Exception { MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); String sender = "[email protected]"; @@ -461,6 +537,61 @@ public class MailQueueRoutesTest { .param("recipient", "[email protected]") .when() .delete(FIRST_QUEUE + "/mails") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void forcingDelayedMailsDeliveryShouldRejectRequestWithoutDelayedParameter() throws Exception { + MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); + + given() + .queryParam("delayed", "true") + .body("{\"xx\": \"false\"}") + .when() + .patch(FIRST_QUEUE + "/mails") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void forcingDelayedMailsDeliveryShouldAcceptRequestWithUnknownFields() throws Exception { + MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); + + given() + .queryParam("delayed", "true") + .body("{" + + "\"xx\": \"false\"," + + "\"delayed\": \"false\"" + + "}") + .when() + .patch(FIRST_QUEUE + "/mails") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void forcingDelayedMailsDeliveryShouldRejectMalformedJsonPayload() throws Exception { + MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); + + given() + .queryParam("delayed", "true") + .body("{\"xx\":") + .when() + .patch(FIRST_QUEUE + "/mails") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test + public void forcingDelayedMailsDeliveryShouldRejectTrueDelayedAttribute() throws Exception { + MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); + + given() + .queryParam("delayed", "false") + .body("{\"delayed\": \"true\"}") + .when() + .patch(FIRST_QUEUE + "/mails") .then() .statusCode(HttpStatus.BAD_REQUEST_400); } @@ -478,6 +609,19 @@ public class MailQueueRoutesTest { } @Test + public void forcingDelayedMailsDeliveryShouldRejectStringDelayedAttribute() throws Exception { + MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); + + given() + .queryParam("delayed", "false") + .body("{\"delayed\": \"string\"}") + .when() + .patch(FIRST_QUEUE + "/mails") + .then() + .statusCode(HttpStatus.BAD_REQUEST_400); + } + + @Test public void deleteMailsShouldDeleteMailsWhenTheyAreMatching() throws Exception { MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); String recipient = "[email protected]"; @@ -501,4 +645,27 @@ public class MailQueueRoutesTest { assertThat(item.getMail().getRecipients()).doesNotContain(deletedRecipientMailAddress); }); } + + @Test + public void forcingDelayedMailsDeliveryShouldActuallyChangePropertyOnMails() throws Exception { + MemoryMailQueue queue = mailQueueFactory.createQueue(FIRST_QUEUE); + FakeMail mail = Mails.defaultMail().build(); + queue.enQueue(mail, 10L, TimeUnit.MINUTES); + queue.enQueue(mail, 10L, TimeUnit.MINUTES); + queue.enQueue(mail); + + with() + .queryParam("delayed", "true") + .body("{\"delayed\": \"false\"}") + .then() + .patch(FIRST_QUEUE + "/mails"); + + assertThat(queue.browse()) + .extracting(ManageableMailQueue.MailQueueItemView::getNextDelivery) + .hasSize(3) + .allSatisfy((delivery) -> { + assertThat(delivery).isNotEmpty(); + assertThat(delivery.get()).isBefore(ZonedDateTime.now()); + }); + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/cae93f35/server/queue/queue-memory/src/main/java/org/apache/james/queue/memory/MemoryMailQueueFactory.java ---------------------------------------------------------------------- diff --git a/server/queue/queue-memory/src/main/java/org/apache/james/queue/memory/MemoryMailQueueFactory.java b/server/queue/queue-memory/src/main/java/org/apache/james/queue/memory/MemoryMailQueueFactory.java index 9049735..a5b4233 100644 --- a/server/queue/queue-memory/src/main/java/org/apache/james/queue/memory/MemoryMailQueueFactory.java +++ b/server/queue/queue-memory/src/main/java/org/apache/james/queue/memory/MemoryMailQueueFactory.java @@ -19,7 +19,6 @@ package org.apache.james.queue.memory; -import java.time.LocalDateTime; import java.time.ZonedDateTime; import java.util.Iterator; import java.util.Optional; @@ -257,7 +256,7 @@ public class MemoryMailQueueFactory implements MailQueueFactory<ManageableMailQu @Override public long getDelay(TimeUnit unit) { - return LocalDateTime.now().until(delivery, Temporals.chronoUnit(unit)); + return ZonedDateTime.now().until(delivery, Temporals.chronoUnit(unit)); } @Override --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
