From dffde374546d7288e8c829fab715e28cc60a91aa Mon Sep 17 00:00:00 2001 From: Giannandrea Castaldi Date: Mon, 22 Jan 2024 11:40:02 +0100 Subject: [PATCH] #550 added QueueSplitterConfigurationParser --- .../splitter/QueueSplitterConfiguration.java | 93 ++++++++++ .../QueueSplitterConfigurationParser.java | 80 +++++++++ .../splitter/QueueSplitterHandler.java | 9 + .../QueueSplitterConfigurationParserTest.java | 161 ++++++++++++++++++ ...source_queuesplitter_configuration_invalid | 13 ++ ...e_queuesplitter_configuration_invalid_json | 10 ++ ...resource_queuesplitter_configuration_valid | 18 ++ ...rce_queuesplitter_configuration_with_props | 6 + 8 files changed, 390 insertions(+) create mode 100644 gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfiguration.java create mode 100644 gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfigurationParser.java create mode 100644 gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterHandler.java create mode 100644 gateleen-queue/src/test/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfigurationParserTest.java create mode 100644 gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_invalid create mode 100644 gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_invalid_json create mode 100644 gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_valid create mode 100644 gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_with_props diff --git a/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfiguration.java b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfiguration.java new file mode 100644 index 000000000..430193088 --- /dev/null +++ b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfiguration.java @@ -0,0 +1,93 @@ +package org.swisspush.gateleen.queue.queuing.splitter; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * Container holding configuration values for {@link QueueSplitterHandler} identified + * by a queue pattern. + * + * @author https://github.com/gcastaldi [Giannandrea Castaldi] + */ +public class QueueSplitterConfiguration { + + private final Pattern queue; + + private final String postfixDelimiter; + + @Nullable + private final List postfixFromStatic; + + @Nullable + private final String postfixFromHeader; + + @Nullable + private final String postfixFromUrl; + + + public QueueSplitterConfiguration( + Pattern queue, + String postfixDelimiter, + @Nullable List postfixFromStatic, + @Nullable String postfixFromHeader, + @Nullable String postfixFromUrl) { + this.queue = queue; + this.postfixDelimiter = postfixDelimiter; + this.postfixFromStatic = postfixFromStatic; + this.postfixFromHeader = postfixFromHeader; + this.postfixFromUrl = postfixFromUrl; + } + + public Pattern getQueue() { + return queue; + } + + public String getPostfixDelimiter() { + return postfixDelimiter; + } + + @Nullable + public List getPostfixFromStatic() { + return postfixFromStatic; + } + + @Nullable + public String getPostfixFromHeader() { + return postfixFromHeader; + } + + @Nullable + public String getPostfixFromUrl() { + return postfixFromUrl; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + QueueSplitterConfiguration that = (QueueSplitterConfiguration) o; + return Objects.equals(queue, that.queue) && + Objects.equals(postfixDelimiter, that.postfixDelimiter) && + Objects.equals(postfixFromStatic, that.postfixFromStatic) && + Objects.equals(postfixFromHeader, that.postfixFromHeader) && + Objects.equals(postfixFromUrl, that.postfixFromUrl); + } + + @Override + public int hashCode() { + return Objects.hash(queue, postfixDelimiter, postfixFromStatic, postfixFromHeader, postfixFromUrl); + } + + @Override + public String toString() { + return "QueueSplitterConfiguration{" + + "queue=" + queue + + ", postfixDelimiter='" + postfixDelimiter + '\'' + + ", postfixFromStatic=" + postfixFromStatic + + ", postfixFromHeader='" + postfixFromHeader + '\'' + + ", postfixFromUrl='" + postfixFromUrl + '\'' + + '}'; + } +} diff --git a/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfigurationParser.java b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfigurationParser.java new file mode 100644 index 000000000..c27251bca --- /dev/null +++ b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfigurationParser.java @@ -0,0 +1,80 @@ +package org.swisspush.gateleen.queue.queuing.splitter; + +import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.JsonArray; +import io.vertx.core.json.JsonObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.swisspush.gateleen.core.util.StringUtils; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Parses the splitter configuration resource in to a list of {@link QueueSplitterConfiguration} + * + * @author https://github.com/gcastaldi [Giannandrea Castaldi] + */ +public class QueueSplitterConfigurationParser { + + private static final Logger log = LoggerFactory.getLogger(QueueSplitterConfigurationParser.class); + public static final String POSTFIX_FROM_STATIC_KEY = "postfixFromStatic"; + public static final String POSTFIX_FROM_HEADER_KEY = "postfixFromHeader"; + public static final String POSTFIX_FROM_URL_KEY = "postfixFromUrl"; + public static final String POSTFIX_DELIMITER_KEY = "postfixDelimiter"; + public static final String DEFAULT_POSTFIX_DELIMITER = "-"; + + static List parse(Buffer configurationResourceBuffer, Map properties) { + + JsonObject config; + List queueSplitterConfigurations = new ArrayList<>(); + try { + String resolvedConfiguration = StringUtils.replaceWildcardConfigs( + configurationResourceBuffer.toString(StandardCharsets.UTF_8), + properties + ); + config = new JsonObject(Buffer.buffer(resolvedConfiguration)); + } catch (Exception ex) { + log.warn("Could not replace wildcards with environment properties for queue splitter configurations or json invalid. Here the reason: {}", + ex.getMessage()); + return queueSplitterConfigurations; + } + + for (String queuePattern : config.fieldNames()) { + Pattern pattern = Pattern.compile(queuePattern); + JsonObject queueConfig = config.getJsonObject(queuePattern); + JsonArray postfixFromStatic = queueConfig.getJsonArray(POSTFIX_FROM_STATIC_KEY); + if (postfixFromStatic != null) { + List staticPostfixes = postfixFromStatic.stream().map(Object::toString).collect(Collectors.toList()); + queueSplitterConfigurations.add(new QueueSplitterConfiguration( + pattern, + queueConfig.getString("postfixDelimiter", DEFAULT_POSTFIX_DELIMITER), + staticPostfixes, + null, + null + )); + continue; + } + String postfixFromHeader = queueConfig.getString(POSTFIX_FROM_HEADER_KEY); + String postfixFromUrl = queueConfig.getString(POSTFIX_FROM_URL_KEY); + if (postfixFromHeader != null || postfixFromUrl != null) { + queueSplitterConfigurations.add(new QueueSplitterConfiguration( + pattern, + queueConfig.getString(POSTFIX_DELIMITER_KEY, DEFAULT_POSTFIX_DELIMITER), + null, + postfixFromHeader, + postfixFromUrl + )); + } else { + log.warn("Queue splitter configuration without a postfix definition"); + } + } + + return queueSplitterConfigurations; + } + +} diff --git a/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterHandler.java b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterHandler.java new file mode 100644 index 000000000..2de873a43 --- /dev/null +++ b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterHandler.java @@ -0,0 +1,9 @@ +package org.swisspush.gateleen.queue.queuing.splitter; + +/** + * Handler class for queues configured to be split in sub-queues. + * + * @author https://github.com/gcastaldi [Giannandrea Castaldi] + */ +public class QueueSplitterHandler { +} diff --git a/gateleen-queue/src/test/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfigurationParserTest.java b/gateleen-queue/src/test/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfigurationParserTest.java new file mode 100644 index 000000000..b08eefebc --- /dev/null +++ b/gateleen-queue/src/test/java/org/swisspush/gateleen/queue/queuing/splitter/QueueSplitterConfigurationParserTest.java @@ -0,0 +1,161 @@ +package org.swisspush.gateleen.queue.queuing.splitter; + +import io.vertx.core.buffer.Buffer; +import io.vertx.ext.unit.TestContext; +import io.vertx.ext.unit.junit.VertxUnitRunner; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.swisspush.gateleen.core.util.ResourcesUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * Tests for {@link QueueSplitterConfigurationParser} class + * + * @author https://github.com/gcastaldi [Giannandrea Castaldi] + */ +@RunWith(VertxUnitRunner.class) +public class QueueSplitterConfigurationParserTest { + + private final String CONFIGURATION_VALID = ResourcesUtils.loadResource( + "testresource_queuesplitter_configuration_valid", + true + ); + + private final String CONFIGURATION_INVALID = ResourcesUtils.loadResource( + "testresource_queuesplitter_configuration_invalid", + true + ); + + private final String CONFIGURATION_INVALID_JSON = ResourcesUtils.loadResource( + "testresource_queuesplitter_configuration_invalid_json", + true + ); + + private final String CONFIGURATION_WITH_PROPS = ResourcesUtils.loadResource( + "testresource_queuesplitter_configuration_with_props", + true + ); + + @Test + public void parseWithAllValid(TestContext context) { + + // Given + Buffer configurationResourceBuffer = Buffer.buffer(CONFIGURATION_VALID); + HashMap properties = new HashMap<>(); + + // When + List configurations = QueueSplitterConfigurationParser.parse( + configurationResourceBuffer, + properties + ); + + // Then + context.assertEquals(3, configurations.size()); + + // Note that the order of the parsed configurations matters! + QueueSplitterConfiguration config_1 = configurations.get(0); + context.assertEquals(Pattern.compile("my-queue-1").pattern(), config_1.getQueue().pattern()); + context.assertEquals("-", config_1.getPostfixDelimiter()); + context.assertEquals(List.of("A", "B", "C", "D"), config_1.getPostfixFromStatic()); + context.assertNull(config_1.getPostfixFromHeader()); + context.assertNull(config_1.getPostfixFromUrl()); + + QueueSplitterConfiguration config_2 = configurations.get(1); + context.assertEquals(Pattern.compile("my-queue-[0-9]+").pattern(), config_2.getQueue().pattern()); + context.assertEquals("+", config_2.getPostfixDelimiter()); + context.assertNull(config_2.getPostfixFromStatic()); + context.assertEquals("{x-rp-deviceid}", config_2.getPostfixFromHeader()); + context.assertNull(config_2.getPostfixFromUrl()); + + QueueSplitterConfiguration config_3 = configurations.get(2); + context.assertEquals(Pattern.compile("my-queue-[a-zA-Z]+").pattern(), config_3.getQueue().pattern()); + context.assertEquals("_", config_3.getPostfixDelimiter()); + context.assertNull(config_3.getPostfixFromStatic()); + context.assertNull(config_3.getPostfixFromHeader()); + context.assertEquals(".*/path1/(.*)/path3/path4/.*", config_3.getPostfixFromUrl()); + } + + @Test + public void parseWithOneValidOneNot(TestContext context) { + + // Given + Buffer configurationResourceBuffer = Buffer.buffer(CONFIGURATION_INVALID); + HashMap properties = new HashMap<>(); + + // When + List configurations = QueueSplitterConfigurationParser.parse( + configurationResourceBuffer, + properties + ); + + // Then + context.assertEquals(1, configurations.size()); + + QueueSplitterConfiguration config_1 = configurations.get(0); + context.assertEquals(Pattern.compile("my-queue-1").pattern(), config_1.getQueue().pattern()); + context.assertEquals("-", config_1.getPostfixDelimiter()); + context.assertEquals(List.of("A", "B", "C", "D"), config_1.getPostfixFromStatic()); + context.assertNull(config_1.getPostfixFromHeader()); + context.assertNull(config_1.getPostfixFromUrl()); + } + @Test + public void parseWithInvalidJson(TestContext context) { + + // Given + Buffer configurationResourceBuffer = Buffer.buffer(CONFIGURATION_INVALID_JSON); + HashMap properties = new HashMap<>(); + + // When + List configurations = QueueSplitterConfigurationParser.parse( + configurationResourceBuffer, + properties + ); + + // Then + context.assertEquals(0, configurations.size()); + } + + @Test + public void parseWithValidAndProps(TestContext context) { + + // Given + Buffer configurationResourceBuffer = Buffer.buffer(CONFIGURATION_WITH_PROPS); + Map properties = Map.of("queue.splitter.delimiter", "_"); + + // When + List configurations = QueueSplitterConfigurationParser.parse( + configurationResourceBuffer, + properties + ); + + // Then + context.assertEquals(1, configurations.size()); + + QueueSplitterConfiguration config_1 = configurations.get(0); + context.assertEquals(Pattern.compile("my-queue-[0-9]+").pattern(), config_1.getQueue().pattern()); + context.assertEquals("_", config_1.getPostfixDelimiter()); + context.assertNull(config_1.getPostfixFromStatic()); + context.assertEquals("{x-rp-deviceid}", config_1.getPostfixFromHeader()); + context.assertNull(config_1.getPostfixFromUrl()); + } + @Test + public void parseWithValidAndMissingProps(TestContext context) { + + // Given + Buffer configurationResourceBuffer = Buffer.buffer(CONFIGURATION_WITH_PROPS); + Map properties = new HashMap<>(); + + // When + List configurations = QueueSplitterConfigurationParser.parse( + configurationResourceBuffer, + properties + ); + + // Then + context.assertEquals(0, configurations.size()); + } +} diff --git a/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_invalid b/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_invalid new file mode 100644 index 000000000..f4522fb7a --- /dev/null +++ b/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_invalid @@ -0,0 +1,13 @@ +{ + "my-queue-1" : { + "postfixFromStatic": [ + "A", + "B", + "C", + "D" + ] + }, + "my-queue-[0-9]+" : { + "postfixDelimiter": "+" + } +} \ No newline at end of file diff --git a/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_invalid_json b/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_invalid_json new file mode 100644 index 000000000..3fdbfbc82 --- /dev/null +++ b/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_invalid_json @@ -0,0 +1,10 @@ +{ + "my-queue-1" : { + "postfixFromStatic": [ + "A" + "B" + "C" + "D" + ] + } +} \ No newline at end of file diff --git a/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_valid b/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_valid new file mode 100644 index 000000000..c4ae7d7c1 --- /dev/null +++ b/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_valid @@ -0,0 +1,18 @@ +{ + "my-queue-1" : { + "postfixFromStatic": [ + "A", + "B", + "C", + "D" + ] + }, + "my-queue-[0-9]+" : { + "postfixDelimiter": "+", + "postfixFromHeader": "{x-rp-deviceid}" + }, + "my-queue-[a-zA-Z]+" : { + "postfixDelimiter": "_", + "postfixFromUrl": ".*/path1/(.*)/path3/path4/.*" + } +} \ No newline at end of file diff --git a/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_with_props b/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_with_props new file mode 100644 index 000000000..ae03103c8 --- /dev/null +++ b/gateleen-queue/src/test/resources/testresource_queuesplitter_configuration_with_props @@ -0,0 +1,6 @@ +{ + "my-queue-[0-9]+" : { + "postfixDelimiter": "${queue.splitter.delimiter}", + "postfixFromHeader": "{x-rp-deviceid}" + } +} \ No newline at end of file