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 68dcf80e55e086cd3506dfdbf62259343938d180
Author: tranduong <[email protected]>
AuthorDate: Mon Jul 29 10:05:46 2019 +0700

    JAMES-2832 RegexMappingRoutes implementation and documentation
---
 .../james/modules/server/DataRoutesModules.java    |   2 +
 .../james/rrt/api/InvalidRegexException.java       |  26 ++++
 .../rrt/lib/AbstractRecipientRewriteTable.java     |   3 +-
 .../test/resources/cucumber/rewrite_tables.feature |   2 +-
 .../james/webadmin/routes/RegexMappingRoutes.java  | 116 +++++++++++++++
 .../apache/james/webadmin/routes/UserRoutes.java   |   5 +-
 .../webadmin/routes/RegexMappingRoutesTest.java    | 159 +++++++++++++++++++++
 src/site/markdown/server/manage-webadmin.md        |  43 +++++-
 8 files changed, 347 insertions(+), 9 deletions(-)

diff --git 
a/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java
 
b/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java
index 9f8c8a56..d198069 100644
--- 
a/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java
+++ 
b/server/container/guice/protocols/webadmin-data/src/main/java/org/apache/james/modules/server/DataRoutesModules.java
@@ -28,6 +28,7 @@ import org.apache.james.webadmin.routes.DomainsRoutes;
 import org.apache.james.webadmin.routes.ForwardRoutes;
 import org.apache.james.webadmin.routes.GroupsRoutes;
 import org.apache.james.webadmin.routes.MappingRoutes;
+import org.apache.james.webadmin.routes.RegexMappingRoutes;
 import org.apache.james.webadmin.routes.UserRoutes;
 import org.apache.james.webadmin.utils.JsonTransformerModule;
 
@@ -46,6 +47,7 @@ public class DataRoutesModules extends AbstractModule {
         routesMultibinder.addBinding().to(ForwardRoutes.class);
         routesMultibinder.addBinding().to(GroupsRoutes.class);
         routesMultibinder.addBinding().to(MappingRoutes.class);
+        routesMultibinder.addBinding().to(RegexMappingRoutes.class);
         routesMultibinder.addBinding().to(UserRoutes.class);
 
         Multibinder<JsonTransformerModule> jsonTransformerModuleMultibinder = 
Multibinder.newSetBinder(binder(), JsonTransformerModule.class);
diff --git 
a/server/data/data-api/src/main/java/org/apache/james/rrt/api/InvalidRegexException.java
 
b/server/data/data-api/src/main/java/org/apache/james/rrt/api/InvalidRegexException.java
new file mode 100644
index 0000000..1ae93e8
--- /dev/null
+++ 
b/server/data/data-api/src/main/java/org/apache/james/rrt/api/InvalidRegexException.java
@@ -0,0 +1,26 @@
+/****************************************************************
+ * 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.rrt.api;
+
+public class InvalidRegexException extends RecipientRewriteTableException {
+    public InvalidRegexException(String msg, Exception e) {
+        super(msg, e);
+    }
+}
diff --git 
a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
 
b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
index 7af23d4..e8b07e5 100644
--- 
a/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
+++ 
b/server/data/data-library/src/main/java/org/apache/james/rrt/lib/AbstractRecipientRewriteTable.java
@@ -35,6 +35,7 @@ import org.apache.james.core.User;
 import org.apache.james.domainlist.api.DomainList;
 import org.apache.james.domainlist.api.DomainListException;
 import org.apache.james.lifecycle.api.Configurable;
+import org.apache.james.rrt.api.InvalidRegexException;
 import org.apache.james.rrt.api.MappingAlreadyExistsException;
 import org.apache.james.rrt.api.RecipientRewriteTable;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
@@ -182,7 +183,7 @@ public abstract class AbstractRecipientRewriteTable 
implements RecipientRewriteT
         try {
             Pattern.compile(regex);
         } catch (PatternSyntaxException e) {
-            throw new RecipientRewriteTableException("Invalid regex: " + 
regex, e);
+            throw new InvalidRegexException("Invalid regex: " + regex, e);
         }
 
         Mapping mapping = Mapping.regex(regex);
diff --git 
a/server/data/data-library/src/test/resources/cucumber/rewrite_tables.feature 
b/server/data/data-library/src/test/resources/cucumber/rewrite_tables.feature
index 1048672..27802cd 100644
--- 
a/server/data/data-library/src/test/resources/cucumber/rewrite_tables.feature
+++ 
b/server/data/data-library/src/test/resources/cucumber/rewrite_tables.feature
@@ -29,7 +29,7 @@ Feature: Rewrite Tables tests
   @readonly
   Scenario: storing an invalid regexp mapping should not work
     When store an invalid ".*):" regexp mapping for user "test" at domain 
"localhost"
-    Then a "RecipientRewriteTableException" exception should have been thrown
+    Then a "InvalidRegexException" exception should have been thrown
 
 # Address mapping
 
diff --git 
a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/RegexMappingRoutes.java
 
b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/RegexMappingRoutes.java
new file mode 100644
index 0000000..ee2cfd6
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/RegexMappingRoutes.java
@@ -0,0 +1,116 @@
+/****************************************************************
+ * 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.routes;
+
+import static org.apache.james.webadmin.Constants.SEPARATOR;
+import static spark.Spark.halt;
+
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+
+import javax.inject.Inject;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import org.apache.james.core.User;
+import org.apache.james.rrt.api.InvalidRegexException;
+import org.apache.james.rrt.api.RecipientRewriteTable;
+import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.webadmin.Constants;
+import org.apache.james.webadmin.Routes;
+import org.apache.james.webadmin.utils.ErrorResponder;
+import org.eclipse.jetty.http.HttpStatus;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import spark.HaltException;
+import spark.Request;
+import spark.Response;
+import spark.Service;
+
+@Api(tags = "Regex Mapping")
+@Path(RegexMappingRoutes.BASE_PATH)
+@Produces(Constants.JSON_CONTENT_TYPE)
+public class RegexMappingRoutes implements Routes {
+
+    static final String BASE_PATH = "/mappings/regex";
+    static final String MAPPING_SOURCE_PARAM = ":mappingSource";
+    static final String REGEX_PARAM = ":regex";
+    static final String ADD_ADDRESS_MAPPING_PATH = BASE_PATH + SEPARATOR
+        + MAPPING_SOURCE_PARAM + "/targets/" + REGEX_PARAM;
+
+    private final RecipientRewriteTable recipientRewriteTable;
+
+    @Inject
+    @VisibleForTesting
+    RegexMappingRoutes(RecipientRewriteTable recipientRewriteTable) {
+        this.recipientRewriteTable = recipientRewriteTable;
+    }
+
+    @Override
+    public String getBasePath() {
+        return BASE_PATH;
+    }
+
+    @Override
+    public void define(Service service) {
+        service.post(ADD_ADDRESS_MAPPING_PATH, this::addRegexMapping);
+    }
+
+    @POST
+    @Path(ADD_ADDRESS_MAPPING_PATH)
+    @ApiOperation(value = "adding address-regex mappings to 
RecipientRewriteTable")
+    @ApiResponses(value = {
+        @ApiResponse(code = HttpStatus.NO_CONTENT_204, message = "Mapping 
successfully added"),
+        @ApiResponse(code = HttpStatus.BAD_REQUEST_400, message = "Invalid 
parameter")
+    })
+    private HaltException addRegexMapping(Request request, Response response) 
throws Exception {
+        try {
+            MappingSource mappingSource = extractMappingSource(request);
+            String regex = URLDecoder.decode(request.params(REGEX_PARAM), 
StandardCharsets.UTF_8.toString());
+            recipientRewriteTable.addRegexMapping(mappingSource, regex);
+        } catch (InvalidRegexException e) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
+                .message(e.getMessage())
+                .haltError();
+        }
+        return halt(HttpStatus.NO_CONTENT_204);
+    }
+
+    private MappingSource extractMappingSource(Request request) {
+        try {
+            return MappingSource
+                
.fromUser(User.fromUsername(request.params(MAPPING_SOURCE_PARAM)));
+        } catch (IllegalArgumentException e) {
+            throw ErrorResponder.builder()
+                .statusCode(HttpStatus.BAD_REQUEST_400)
+                .type(ErrorResponder.ErrorType.INVALID_ARGUMENT)
+                .message("Invalid `source` field.")
+                .haltError();
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UserRoutes.java
 
b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UserRoutes.java
index 6054d0e..0e7735d 100644
--- 
a/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UserRoutes.java
+++ 
b/server/protocols/webadmin/webadmin-data/src/main/java/org/apache/james/webadmin/routes/UserRoutes.java
@@ -184,7 +184,4 @@ public class UserRoutes implements Routes {
                 .haltError();
         }
     }
-
-
-
-}
+}
\ No newline at end of file
diff --git 
a/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/RegexMappingRoutesTest.java
 
b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/RegexMappingRoutesTest.java
new file mode 100644
index 0000000..1c9aebd
--- /dev/null
+++ 
b/server/protocols/webadmin/webadmin-data/src/test/java/org/apache/james/webadmin/routes/RegexMappingRoutesTest.java
@@ -0,0 +1,159 @@
+/****************************************************************
+ * 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.routes;
+
+import static io.restassured.RestAssured.with;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.core.Is.is;
+import static org.mockito.Mockito.mock;
+
+import org.apache.james.core.Domain;
+import org.apache.james.core.User;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.domainlist.api.DomainList;
+import org.apache.james.domainlist.memory.MemoryDomainList;
+import org.apache.james.rrt.lib.Mapping;
+import org.apache.james.rrt.lib.MappingSource;
+import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
+import org.apache.james.webadmin.WebAdminServer;
+import org.apache.james.webadmin.WebAdminUtils;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.restassured.RestAssured;
+import io.restassured.filter.log.LogDetail;
+import io.restassured.http.ContentType;
+
+class RegexMappingRoutesTest {
+    private WebAdminServer webAdminServer;
+    private MemoryRecipientRewriteTable memoryRecipientRewriteTable;
+
+    @BeforeEach
+    void beforeEach() throws Exception {
+        DNSService dnsService = mock(DNSService.class);
+        DomainList domainList = new MemoryDomainList(dnsService);
+        memoryRecipientRewriteTable = new MemoryRecipientRewriteTable();
+        memoryRecipientRewriteTable.setDomainList(domainList);
+        domainList.addDomain(Domain.of("domain.tld"));
+
+        webAdminServer = WebAdminUtils
+            .createWebAdminServer(new 
RegexMappingRoutes(memoryRecipientRewriteTable))
+            .start();
+
+        RestAssured.requestSpecification = 
WebAdminUtils.buildRequestSpecification(webAdminServer)
+            .setBasePath(RegexMappingRoutes.BASE_PATH)
+            .log(LogDetail.METHOD)
+            .build();
+    }
+
+    @AfterEach
+    void stop() {
+        webAdminServer.destroy();
+    }
+
+    @Test
+    void addRegexMappingShouldReturnNoContentWhenSuccess() {
+        with()
+            .post("[email protected]/targets/bis.*@apache.org")
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204)
+            .contentType(ContentType.JSON);
+
+        assertThat(memoryRecipientRewriteTable
+            
.getStoredMappings(MappingSource.fromUser(User.fromUsername("[email protected]"))))
+            .containsOnly(Mapping.regex("bis.*@apache.org"));
+    }
+
+    @Test
+    void addRegexMappingShouldAllowUserWithoutDomain() {
+        with()
+            .post("jamesdomaintld/targets/bis.*@apache.org")
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204)
+            .contentType(ContentType.JSON);
+
+        assertThat(memoryRecipientRewriteTable
+            
.getStoredMappings(MappingSource.fromUser(User.fromUsername("jamesdomaintld"))))
+            .containsOnly(Mapping.regex("bis.*@apache.org"));
+    }
+
+    @Test
+    void addRegexMappingShouldReturnNotFoundWhenSourceIsEmpty() {
+        with()
+            .post("/targets/bis.*@apache.org")
+        .then()
+            .statusCode(HttpStatus.NOT_FOUND_404)
+            .contentType(ContentType.JSON)
+            .body("type", is("notFound"))
+            .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+            .body("message", is("POST /mappings/regex/targets/bis.*@apache.org 
can not be found"));
+    }
+
+    @Test
+    void addRegexMappingShouldReturnNotFoundWhenRegexIsEmpty() {
+        with()
+            .post("[email protected]/targets/")
+        .then()
+            .statusCode(HttpStatus.NOT_FOUND_404)
+            .contentType(ContentType.JSON)
+            .body("type", is("notFound"))
+            .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+            .body("message", is("POST 
/mappings/regex/[email protected]/targets/ can not be found"));
+    }
+
+    @Test
+    void addRegexMappingShouldReturnNotFoundWhenSourceAndRegexEmpty() {
+        with()
+            .post("/targets/")
+        .then()
+            .statusCode(HttpStatus.NOT_FOUND_404)
+            .contentType(ContentType.JSON)
+            .body("type", is("notFound"))
+            .body("statusCode", is(HttpStatus.NOT_FOUND_404))
+            .body("message", is("POST /mappings/regex/targets/ can not be 
found"));
+    }
+
+    @Test
+    void addRegexMappingShouldReturnBadRequestWhenRegexIsInvalid() {
+        with()
+            .post("[email protected]/targets/O.*[]")
+        .then()
+            .statusCode(HttpStatus.BAD_REQUEST_400)
+            .contentType(ContentType.JSON)
+            .body("type", is("InvalidArgument"))
+            .body("statusCode", is(HttpStatus.BAD_REQUEST_400))
+            .body("message", is("Invalid regex: O.*[]"));
+    }
+
+    @Test
+    void addRegexMappingShouldReturnNoContentWhenRegexContainsQuestionMark() {
+        with()
+            .post("[email protected]/targets/^[aei%3Fou].*[email protected]")
+        .then()
+            .statusCode(HttpStatus.NO_CONTENT_204)
+            .contentType(ContentType.JSON);
+
+        assertThat(memoryRecipientRewriteTable
+            
.getStoredMappings(MappingSource.fromUser(User.fromUsername("[email protected]"))))
+            .containsOnly(Mapping.regex("^[aei?ou].*[email protected]"));
+    }
+}
\ No newline at end of file
diff --git a/src/site/markdown/server/manage-webadmin.md 
b/src/site/markdown/server/manage-webadmin.md
index bc9a08b..812cf9c 100644
--- a/src/site/markdown/server/manage-webadmin.md
+++ b/src/site/markdown/server/manage-webadmin.md
@@ -35,10 +35,11 @@ as exposed above). To avoid information duplication, this 
is ommited on endpoint
  - [Administrating global quotas](#Administrating_global_quotas)
  - [Cassandra Schema upgrades](#Cassandra_Schema_upgrades)
  - [Correcting ghost mailbox](#Correcting_ghost_mailbox)
- - [Creating address group](#Creating_address_group)
- - [Creating address forwards](#Creating_address_forwards)
  - [Creating address aliases](#Creating_address_aliases)
  - [Creating address domain](#Creating_address_domain)
+ - [Creating address forwards](#Creating_address_forwards)
+ - [Creating address group](#Creating_address_group)
+ - [Creating regex mapping](#Creating_regex_mapping)
  - [Address Mappings](#Address_mappings)
  - [Administrating mail repositories](#Administrating_mail_repositories)
  - [Administrating mail queues](#Administrating_mail_queues)
@@ -1456,7 +1457,6 @@ Response codes:
  - 400: Alias structure or member is not valid
 
 ## Creating address domain
-
 You can use **webadmin** to define domain mappings.
 
 Given a configured source (from) domain and a destination (to) domain, when an 
email is sent to an address belonging to the source domain, then the domain 
part of this address is overwritten, the destination domain is then used.
@@ -1546,6 +1546,43 @@ Response codes:
  - 400: The `fromDomain` resource name is invalid
  - 400: The destination domain specified in the body is invalid
 
+## Creating regex mapping
+You can use **webadmin** to create regex mappings.
+
+A regex mapping contains a mapping source and a Java Regular Expression 
(regex) in String as the mapping value. 
+Everytime, if a mail containing a recipient matched with the mapping source, 
+then that mail will be re-routed to a new recipient address 
+which is re written by the regex.
+
+This feature uses [Recipients rewrite 
table](/server/config-recipientrewritetable.html) and 
+requires the [RecipientRewriteTable 
API](https://github.com/apache/james-project/blob/master/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RecipientRewriteTable.java)
+to be configured.
+ - [Adding a regex mapping](#Adding_a_regex_mapping)
+
+### Adding a regex mapping
+
+```
+POST /mappings/regex/mappingSource/targets/regex
+```
+
+Where:
+ - the `mappingSource` is the path parameter represents for the Regex Mapping 
mapping source
+ - the `regex` is the path parameter represents for the Regex Mapping regex
+
+The route will add a regex mapping made from `mappingSource` and `regex` to 
RecipientRewriteTable.
+
+Example:
+
+```
+curl -XPOST 
http://ip:port/mappings/regex/[email protected]/targets/james@.*:[email protected]
+```
+
+Response codes:
+
+ - 204: Mapping added successfully.
+ - 400: Invalid `mappingSource` path parameter.
+ - 400: Invalid `regex` path parameter.
+
 ## Address Mappings
 
 You can use **webadmin** to define address mappings.


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to