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/12/17 01:55:30 UTC

[james-project] 09/24: JAMES-3006 Add a TaskFactory for modular task registration in webadmin

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 2033c68721b77f5e96ce672377a92aeca9cce9f8
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Thu Dec 12 08:27:46 2019 +0100

    JAMES-3006 Add a TaskFactory for modular task registration in webadmin
---
 .../apache/james/webadmin/tasks/TaskFactory.java   | 145 +++++++++++++++++++
 .../apache/james/webadmin/tasks/TaskGenerator.java |  54 +++++++
 .../james/webadmin/tasks/TaskRegistrationKey.java  |  66 +++++++++
 .../james/webadmin/tasks/TaskFactoryTest.java      | 159 +++++++++++++++++++++
 .../webadmin/tasks/TaskRegistrationKeyTest.java    |  53 +++++++
 5 files changed, 477 insertions(+)

diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/tasks/TaskFactory.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/tasks/TaskFactory.java
new file mode 100644
index 0000000..7db9bef
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/tasks/TaskFactory.java
@@ -0,0 +1,145 @@
+/****************************************************************
+ * 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.tasks;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.james.task.Task;
+import org.apache.james.task.TaskManager;
+
+import com.github.fge.lambdas.Throwing;
+import com.github.steveash.guavate.Guavate;
+import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+import spark.Request;
+import spark.Route;
+
+public class TaskFactory implements TaskGenerator {
+    private static final String DEFAULT_PARAMETER = "action";
+
+    public static class Builder {
+        private Optional<String> taskParameterName;
+        private ImmutableMap.Builder<TaskRegistrationKey, TaskGenerator> tasks;
+
+        public Builder() {
+            taskParameterName = Optional.empty();
+            tasks = ImmutableMap.builder();
+        }
+
+        public Builder parameterName(String parameterName) {
+            this.taskParameterName = Optional.of(parameterName);
+            return this;
+        }
+
+        public Builder registrations(TaskRegistration... taskRegistrations) {
+            this.tasks.putAll(Arrays.stream(taskRegistrations)
+                .collect(Guavate.toImmutableMap(
+                    TaskRegistration::registrationKey,
+                    Function.identity())));
+            return this;
+        }
+
+        public Builder register(TaskRegistrationKey key, TaskGenerator taskGenerator) {
+            this.tasks.put(key, taskGenerator);
+            return this;
+        }
+
+        public TaskFactory build() {
+            ImmutableMap<TaskRegistrationKey, TaskGenerator> registrations = tasks.build();
+            Preconditions.checkState(!registrations.isEmpty());
+            return new TaskFactory(
+                taskParameterName.orElse(DEFAULT_PARAMETER),
+                registrations);
+        }
+
+        public Route buildAsRoute(TaskManager taskManager) {
+            return build().asRoute(taskManager);
+        }
+    }
+
+    public static class TaskRegistration implements TaskGenerator {
+        private final TaskRegistrationKey taskRegistrationKey;
+        private final TaskGenerator taskGenerator;
+
+        public TaskRegistration(TaskRegistrationKey taskRegistrationKey, TaskGenerator taskGenerator) {
+            this.taskRegistrationKey = taskRegistrationKey;
+            this.taskGenerator = taskGenerator;
+        }
+
+        public TaskRegistrationKey registrationKey() {
+            return taskRegistrationKey;
+        }
+
+        @Override
+        public Task generate(Request request) throws Exception {
+            return taskGenerator.generate(request);
+        }
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    private final String taskParameterName;
+    private final Map<TaskRegistrationKey, TaskGenerator> taskGenerators;
+
+    private TaskFactory(String taskParameterName, Map<TaskRegistrationKey, TaskGenerator> taskGenerators) {
+        this.taskParameterName = taskParameterName;
+        this.taskGenerators = taskGenerators;
+    }
+
+    @Override
+    public Task generate(Request request) throws Exception {
+        TaskRegistrationKey registrationKey = parseRegistrationKey(request);
+        return Optional.ofNullable(taskGenerators.get(registrationKey))
+            .map(Throwing.<TaskGenerator, Task>function(taskGenerator -> taskGenerator.generate(request)).sneakyThrow())
+            .orElseThrow(() -> new IllegalArgumentException("Invalid value supplied for '" + taskParameterName + "': " + registrationKey.asString()
+                + ". " + supportedValueMessage()));
+    }
+
+    private TaskRegistrationKey parseRegistrationKey(Request request) {
+        return Optional.ofNullable(request.queryParams(taskParameterName))
+            .map(this::validateParameter)
+            .map(TaskRegistrationKey::of)
+            .orElseThrow(() -> new IllegalArgumentException("'" + taskParameterName + "' query parameter is compulsory. " + supportedValueMessage()));
+    }
+
+    private String validateParameter(String parameter) {
+        if (StringUtils.isBlank(parameter)) {
+            throw new IllegalArgumentException("'" + taskParameterName + "' query parameter cannot be empty or blank. " + supportedValueMessage());
+        }
+        return parameter;
+    }
+
+    private String supportedValueMessage() {
+        ImmutableList<String> supportedTasks = taskGenerators.keySet()
+            .stream()
+            .map(TaskRegistrationKey::asString)
+            .collect(Guavate.toImmutableList());
+        return "Supported values are [" + Joiner.on(", ").join(supportedTasks) + "]";
+    }
+}
diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/tasks/TaskGenerator.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/tasks/TaskGenerator.java
new file mode 100644
index 0000000..09d759e
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/tasks/TaskGenerator.java
@@ -0,0 +1,54 @@
+/****************************************************************
+ * 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.tasks;
+
+import org.apache.james.task.Task;
+import org.apache.james.task.TaskId;
+import org.apache.james.task.TaskManager;
+import org.apache.james.webadmin.dto.TaskIdDto;
+
+import spark.Request;
+import spark.Response;
+import spark.Route;
+
+public interface TaskGenerator {
+    class TaskRoute implements Route {
+        private final TaskGenerator taskGenerator;
+        private final TaskManager taskManager;
+
+        TaskRoute(TaskGenerator taskGenerator, TaskManager taskManager) {
+            this.taskGenerator = taskGenerator;
+            this.taskManager = taskManager;
+        }
+
+        @Override
+        public Object handle(Request request, Response response) throws Exception {
+            Task task = taskGenerator.generate(request);
+            TaskId taskId = taskManager.submit(task);
+            return TaskIdDto.respond(response, taskId);
+        }
+    }
+
+    Task generate(Request request) throws Exception;
+
+    default Route asRoute(TaskManager taskManager) {
+        return new TaskRoute(this, taskManager);
+    }
+}
diff --git a/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/tasks/TaskRegistrationKey.java b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/tasks/TaskRegistrationKey.java
new file mode 100644
index 0000000..cf1e55e
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-core/src/main/java/org/apache/james/webadmin/tasks/TaskRegistrationKey.java
@@ -0,0 +1,66 @@
+/****************************************************************
+ * 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.tasks;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+public class TaskRegistrationKey {
+    public static TaskRegistrationKey of(String value) {
+        Preconditions.checkNotNull(value, "TaskRegistrationKey cannot be null");
+        Preconditions.checkArgument(!value.isEmpty(), "TaskRegistrationKey cannot be empty");
+
+        return new TaskRegistrationKey(value);
+    }
+
+    private final String value;
+
+    private TaskRegistrationKey(String value) {
+        this.value = value;
+    }
+
+    public String asString() {
+        return value;
+    }
+
+    @Override
+    public final boolean equals(Object o) {
+        if (o instanceof TaskRegistrationKey) {
+            TaskRegistrationKey that = (TaskRegistrationKey) o;
+
+            return Objects.equals(this.value, that.value);
+        }
+        return false;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(value);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+            .add("value", value)
+            .toString();
+    }
+}
diff --git a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/tasks/TaskFactoryTest.java b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/tasks/TaskFactoryTest.java
new file mode 100644
index 0000000..648fc84
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/tasks/TaskFactoryTest.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.tasks;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import org.apache.james.task.Task;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import spark.Request;
+
+class TaskFactoryTest {
+    static final Task TASK_1 = mock(Task.class);
+    static final Task TASK_2 = mock(Task.class);
+    static final TaskRegistrationKey KEY_1 = TaskRegistrationKey.of("task1");
+    static final TaskRegistrationKey KEY_2 = TaskRegistrationKey.of("task2");
+
+    Request request;
+    TaskFactory taskFactory;
+    TaskFactory singleTaskFactory;
+
+    @BeforeEach
+    void setUp() {
+        request = mock(Request.class);
+        taskFactory = TaskFactory.builder()
+            .register(KEY_1, any -> TASK_1)
+            .register(KEY_2, any -> TASK_2)
+            .build();
+        singleTaskFactory = TaskFactory.builder()
+            .register(KEY_1, any -> TASK_1)
+            .build();
+    }
+
+    @Test
+    void buildShouldThrowWhenNoTasks() {
+        assertThatThrownBy(() -> TaskFactory.builder().build())
+            .isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    void generateShouldThrowFormattedMessageWhenNoTaskParamAndSeveralOptions() {
+        assertThatThrownBy(() -> taskFactory.generate(request))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("'action' query parameter is compulsory. Supported values are [task1, task2]");
+    }
+
+    @Test
+    void generateShouldThrowFormattedMessageWhenNoTaskParam() {
+        assertThatThrownBy(() -> singleTaskFactory.generate(request))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("'action' query parameter is compulsory. Supported values are [task1]");
+    }
+
+    @Test
+    void generateShouldThrowWhenCustomParameterValueIsInvalid() {
+        TaskFactory taskFactory = TaskFactory.builder()
+            .parameterName("custom")
+            .register(KEY_1, any -> TASK_1)
+            .build();
+
+        when(request.queryParams("custom")).thenReturn("unknown");
+
+        assertThatThrownBy(() -> taskFactory.generate(request))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("Invalid value supplied for 'custom': unknown. Supported values are [task1]");
+    }
+
+    @Test
+    void generateShouldThrowWhenCustomParameterNotSpecified() {
+        TaskFactory taskFactory = TaskFactory.builder()
+            .parameterName("custom")
+            .register(KEY_1, any -> TASK_1)
+            .build();
+
+        when(request.queryParams("action")).thenReturn("unknown");
+
+        assertThatThrownBy(() -> taskFactory.generate(request))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("'custom' query parameter is compulsory. Supported values are [task1]");
+    }
+
+    @Test
+    void generateShouldThrowFormattedMessageWhenUnknownTaskParamAndSeveralOptions() {
+        when(request.queryParams("action")).thenReturn("unknown");
+
+        assertThatThrownBy(() -> taskFactory.generate(request))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("Invalid value supplied for 'action': unknown. Supported values are [task1, task2]");
+    }
+
+    @Test
+    void generateShouldThrowFormattedMessageWhenUnknownTaskParam() {
+        when(request.queryParams("action")).thenReturn("unknown");
+
+        assertThatThrownBy(() -> singleTaskFactory.generate(request))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("Invalid value supplied for 'action': unknown. Supported values are [task1]");
+    }
+
+    @Test
+    void generateShouldThrowWhenEmptyTaskParam() {
+        when(request.queryParams("action")).thenReturn("");
+
+        assertThatThrownBy(() -> singleTaskFactory.generate(request))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("'action' query parameter cannot be empty or blank. Supported values are [task1]");
+    }
+
+    @Test
+    void generateShouldThrowWhenBlankTaskParam() {
+        when(request.queryParams("action")).thenReturn(" ");
+
+        assertThatThrownBy(() -> singleTaskFactory.generate(request))
+            .isInstanceOf(IllegalArgumentException.class)
+            .hasMessage("'action' query parameter cannot be empty or blank. Supported values are [task1]");
+    }
+
+    @Test
+    void generateShouldCreateCorrespondingTask() throws Exception {
+        when(request.queryParams("action")).thenReturn("task1");
+
+        assertThat(singleTaskFactory.generate(request))
+            .isSameAs(TASK_1);
+    }
+
+    @Test
+    void generateShouldHandleCustomTaskParameter() throws Exception {
+        TaskFactory taskFactory = TaskFactory.builder()
+            .parameterName("custom")
+            .register(KEY_1, any -> TASK_1)
+            .build();
+
+        when(request.queryParams("custom")).thenReturn("task1");
+
+        assertThat(taskFactory.generate(request))
+            .isSameAs(TASK_1);
+    }
+}
\ No newline at end of file
diff --git a/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/tasks/TaskRegistrationKeyTest.java b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/tasks/TaskRegistrationKeyTest.java
new file mode 100644
index 0000000..3d7babe
--- /dev/null
+++ b/server/protocols/webadmin/webadmin-core/src/test/java/org/apache/james/webadmin/tasks/TaskRegistrationKeyTest.java
@@ -0,0 +1,53 @@
+/****************************************************************
+ * 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.tasks;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+class TaskRegistrationKeyTest {
+    @Test
+    void shouldMatchBeanContract() {
+        EqualsVerifier.forClass(TaskRegistrationKey.class)
+            .verify();
+    }
+
+    @Test
+    void ofShouldThrowWhenNull() {
+        assertThatThrownBy(() -> TaskRegistrationKey.of(null))
+            .isInstanceOf(NullPointerException.class);
+    }
+
+    @Test
+    void ofShouldThrowWhenEmpty() {
+        assertThatThrownBy(() -> TaskRegistrationKey.of(""))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void asStringShouldReturnRawValue() {
+        assertThat(TaskRegistrationKey.of("reindex").asString())
+            .isEqualTo("reindex");
+    }
+}
\ No newline at end of file


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