diff --git a/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerate.json b/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerate.json index 5b64c35a..ba979e26 100644 --- a/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerate.json +++ b/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerate.json @@ -4,5 +4,5 @@ "iterations" : 4, "threads" : 1, "forks" : 3, - "mean_ops" : 823635.7718335792 + "mean_ops" : 824615.1373750888 } \ No newline at end of file diff --git a/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerateBase36.json b/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerateBase36.json index fe461876..e99e8ae7 100644 --- a/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerateBase36.json +++ b/ranger-discovery-bundle/perf/results/io.appform.ranger.discovery.bundle.id.IdGeneratorPerfTest.testGenerateBase36.json @@ -4,5 +4,5 @@ "iterations" : 4, "threads" : 1, "forks" : 3, - "mean_ops" : 655010.8023043653 + "mean_ops" : 668869.350006137 } \ No newline at end of file diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/Id.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/Id.java index 575b43bb..6d9d14e2 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/Id.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/Id.java @@ -30,6 +30,8 @@ @ToString public class Id { private String id; + private String prefix; + private String suffix; private Date generatedDate; private int node; private int exponent; diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/Base36IdFormatter.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/Base36IdFormatter.java index 15db24bb..e53aac78 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/Base36IdFormatter.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/Base36IdFormatter.java @@ -29,6 +29,11 @@ public Base36IdFormatter(IdFormatter idFormatter) { this.idFormatter = idFormatter; } + @Override + public IdParserType getType() { + throw new UnsupportedOperationException(); + } + @Override public String format(final DateTime dateTime, final int nodeId, diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/DefaultIdFormatter.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/DefaultIdFormatter.java index cac2bbcb..3cb6ac51 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/DefaultIdFormatter.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/DefaultIdFormatter.java @@ -28,6 +28,11 @@ public class DefaultIdFormatter implements IdFormatter { private static final Pattern PATTERN = Pattern.compile("(.*)([0-9]{15})([0-9]{4})([0-9]{3})"); private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyMMddHHmmssSSS"); + @Override + public IdParserType getType() { + return IdParserType.DEFAULT; + } + @Override public String format(final DateTime dateTime, final int nodeId, diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatter.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatter.java index 9cd3219e..0fa33809 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatter.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatter.java @@ -22,6 +22,8 @@ public interface IdFormatter { + IdParserType getType(); + String format(final DateTime dateTime, final int nodeId, final int randomNonce); diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatters.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatters.java index b0ea164f..8585e430 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatters.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdFormatters.java @@ -22,6 +22,7 @@ public class IdFormatters { private static final IdFormatter originalIdFormatter = new DefaultIdFormatter(); private static final IdFormatter base36IdFormatter = new Base36IdFormatter(originalIdFormatter); + private static final IdFormatter suffixIdFormatter = new SuffixIdFormatter(); public static IdFormatter original() { return originalIdFormatter; @@ -31,4 +32,8 @@ public static IdFormatter base36() { return base36IdFormatter; } + public static IdFormatter suffix() { + return suffixIdFormatter; + } + } diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParserType.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParserType.java new file mode 100644 index 00000000..254ebfd3 --- /dev/null +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParserType.java @@ -0,0 +1,15 @@ +package io.appform.ranger.discovery.bundle.id.formatter; + +import lombok.Getter; + +@Getter +public enum IdParserType { + DEFAULT (0), + SUFFIX (11); + + private final int value; + + IdParserType(final int value) { + this.value = value; + } +} diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParsers.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParsers.java index 00b3520a..c5e9a32c 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParsers.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/IdParsers.java @@ -20,14 +20,21 @@ import lombok.extern.slf4j.Slf4j; import lombok.val; +import java.util.Map; import java.util.Optional; import java.util.regex.Pattern; + @Slf4j @UtilityClass public class IdParsers { private static final int MINIMUM_ID_LENGTH = 22; - private static final Pattern PATTERN = Pattern.compile("(.*)([0-9]{22})"); + private static final Pattern PATTERN = Pattern.compile("([A-Za-z]*)([0-9]{22})([0-9]{2})?(.*)"); + + private final Map parserRegistry = Map.of( + IdFormatters.original().getType().getValue(), IdFormatters.original(), + IdFormatters.suffix().getType().getValue(), IdFormatters.suffix() + ); /** * Parse the given string to get ID @@ -44,7 +51,18 @@ public Optional parse(final String idString) { if (!matcher.find()) { return Optional.empty(); } - return IdFormatters.original().parse(idString); + + val parserType = matcher.group(3); + if (parserType == null) { + return IdFormatters.original().parse(idString); + } + + val parser = parserRegistry.get(Integer.parseInt(matcher.group(3))); + if (parser == null) { + log.warn("Could not parse idString {}, Invalid formatter type {}", idString, parserType); + return Optional.empty(); + } + return parser.parse(idString); } catch (Exception e) { log.warn("Could not parse idString {}", e.getMessage()); return Optional.empty(); diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/SuffixIdFormatter.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/SuffixIdFormatter.java new file mode 100644 index 00000000..7fdea2c4 --- /dev/null +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/formatter/SuffixIdFormatter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2024 Authors, Flipkart Internet Pvt. Ltd. + * + * Licensed 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 io.appform.ranger.discovery.bundle.id.formatter; + +import io.appform.ranger.discovery.bundle.id.Id; +import lombok.val; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; + +import java.util.Optional; +import java.util.regex.Pattern; + +public class SuffixIdFormatter implements IdFormatter { + private static final Pattern PATTERN = Pattern.compile("([A-Za-z]*)([0-9]{15})([0-9]{4})([0-9]{3})([0-9]{2})([0-9]*)"); + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyMMddHHmmssSSS"); + + @Override + public IdParserType getType() { + return IdParserType.SUFFIX; + } + + @Override + public String format(final DateTime dateTime, + final int nodeId, + final int randomNonce) { + return String.format("%s%04d%03d%02d", DATE_TIME_FORMATTER.print(dateTime), nodeId, randomNonce, getType().getValue()); + } + + @Override + public Optional parse(final String idString) { + val matcher = PATTERN.matcher(idString); + if (!matcher.find()) { + return Optional.empty(); + } + return Optional.of(Id.builder() + .id(idString) + .prefix(matcher.group(1)) + .suffix(matcher.group(6)) + .node(Integer.parseInt(matcher.group(3))) + .exponent(Integer.parseInt(matcher.group(4))) + .generatedDate(DATE_TIME_FORMATTER.parseDateTime(matcher.group(2)).toDate()) + .build()); + } +} diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/DefaultIdGenerator.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/DefaultIdGenerator.java index c66b5929..7f55f7f1 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/DefaultIdGenerator.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/DefaultIdGenerator.java @@ -1,6 +1,8 @@ package io.appform.ranger.discovery.bundle.id.generator; +import io.appform.ranger.discovery.bundle.id.formatter.IdFormatter; import io.appform.ranger.discovery.bundle.id.formatter.IdFormatters; +import io.appform.ranger.discovery.bundle.id.nonce.NonceGenerator; import io.appform.ranger.discovery.bundle.id.nonce.RandomNonceGenerator; public class DefaultIdGenerator extends IdGeneratorBase { @@ -8,4 +10,8 @@ public class DefaultIdGenerator extends IdGeneratorBase { public DefaultIdGenerator() { super(IdFormatters.original(), new RandomNonceGenerator()); } + + public DefaultIdGenerator(final IdFormatter idFormatter) { + super(idFormatter, new RandomNonceGenerator()); + } } diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/IdGeneratorBase.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/IdGeneratorBase.java index a91d91ca..cffa264d 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/IdGeneratorBase.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/generator/IdGeneratorBase.java @@ -94,10 +94,25 @@ public final Id getIdFromIdInfo(final NonceInfo nonceInfo, final String namespac .build(); } + public final Id getIdFromIdInfo(final NonceInfo nonceInfo, final String namespace, final String suffix, final IdFormatter idFormatter) { + val dateTime = new DateTime(nonceInfo.getTime()); + val id = String.format("%s%s%s", namespace, idFormatter.format(dateTime, getNodeId(), nonceInfo.getExponent()), suffix != null ? suffix : ""); + return Id.builder() + .id(id) + .exponent(nonceInfo.getExponent()) + .generatedDate(dateTime.toDate()) + .node(getNodeId()) + .build(); + } + public final Id getIdFromIdInfo(final NonceInfo nonceInfo, final String namespace) { return getIdFromIdInfo(nonceInfo, namespace, idFormatter); } + public final Id getIdFromIdInfo(final NonceInfo nonceInfo, final String namespace, final String suffix) { + return getIdFromIdInfo(nonceInfo, namespace, suffix, idFormatter); + } + public final IdValidationState validateId(final List inConstraints, final Id id, final boolean skipGlobal) { // First evaluate global constraints val failedGlobalConstraint @@ -139,6 +154,10 @@ public final Id generate(final String namespace) { return getIdFromIdInfo(idInfo, namespace); } + public final Id generate(final String namespace, final String suffix) { + val idInfo = nonceGenerator.generate(namespace); + return getIdFromIdInfo(idInfo, namespace, suffix); + } public final Id generate(final String namespace, final IdFormatter idFormatter) { val idInfo = nonceGenerator.generate(namespace); @@ -167,16 +186,29 @@ public final Optional generateWithConstraints(final String namespace, final return generateWithConstraints(request); } + public Optional generateWithConstraints( + String namespace, + String suffix, + final List inConstraints) { + return generateWithConstraints(IdGenerationRequest.builder() + .prefix(namespace) + .constraints(inConstraints) + .skipGlobal(false) + .idFormatter(idFormatter) + .build()); + } + public final Optional generateWithConstraints(final IdGenerationRequest request) { val domain = request.getDomain() != null ? registeredDomains.getOrDefault(request.getDomain(), Domain.DEFAULT) : Domain.DEFAULT; val idGenerationInput = IdGenerationInput.builder() .prefix(request.getPrefix()) + .suffix(request.getSuffix()) .domain(domain) .build(); return Optional.ofNullable(retryer.get( () -> { val idInfoOptional = nonceGenerator.generateWithConstraints(idGenerationInput); - val id = getIdFromIdInfo(idInfoOptional, request.getPrefix(), request.getIdFormatter()); + val id = getIdFromIdInfo(idInfoOptional, request.getPrefix(), request.getSuffix(), request.getIdFormatter()); return new GenerationResult( idInfoOptional, validateId(request.getConstraints(), id, request.isSkipGlobal()), diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationInput.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationInput.java index 4a251748..55436cce 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationInput.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationInput.java @@ -8,6 +8,7 @@ @Builder public class IdGenerationInput { String prefix; + String suffix; Domain domain; } diff --git a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationRequest.java b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationRequest.java index a4affc21..8ad36159 100644 --- a/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationRequest.java +++ b/ranger-discovery-bundle/src/main/java/io/appform/ranger/discovery/bundle/id/request/IdGenerationRequest.java @@ -27,6 +27,7 @@ public class IdGenerationRequest { String prefix; + String suffix; String domain; boolean skipGlobal; List constraints; diff --git a/ranger-discovery-bundle/src/test/java/io/appform/ranger/discovery/bundle/id/IdGeneratorTest.java b/ranger-discovery-bundle/src/test/java/io/appform/ranger/discovery/bundle/id/IdGeneratorTest.java index b7c70371..d2bff3c7 100644 --- a/ranger-discovery-bundle/src/test/java/io/appform/ranger/discovery/bundle/id/IdGeneratorTest.java +++ b/ranger-discovery-bundle/src/test/java/io/appform/ranger/discovery/bundle/id/IdGeneratorTest.java @@ -181,7 +181,7 @@ void testConstraintFailure() { @Test void testNodeId() { - val generatedId = IdGenerator.generate("TEST123"); + val generatedId = IdGenerator.generate("TEST"); val parsedId = IdGenerator.parse(generatedId.getId()).orElse(null); Assertions.assertNotNull(parsedId); Assertions.assertEquals(parsedId.getNode(), nodeId); @@ -225,9 +225,17 @@ void testParseSuccess() { } @Test - void testParseSuccessAfterGeneration() { + void testParseFailAfterGeneration() { val generatedId = IdGenerator.generate("TEST123"); val parsedId = IdGenerator.parse(generatedId.getId()).orElse(null); + Assertions.assertNull(parsedId); + } + + @Test + void testParseSuccessAfterGeneration() { + val prefix = "TEST"; + val generatedId = IdGenerator.generate(prefix); + val parsedId = IdGenerator.parse(generatedId.getId()).orElse(null); Assertions.assertNotNull(parsedId); Assertions.assertEquals(parsedId.getId(), generatedId.getId()); Assertions.assertEquals(parsedId.getExponent(), generatedId.getExponent()); @@ -235,7 +243,6 @@ void testParseSuccessAfterGeneration() { Assertions.assertEquals(parsedId.getGeneratedDate(), generatedId.getGeneratedDate()); } - @SuppressWarnings("SameParameterValue") private Date generateDate(int year, int month, int day, int hour, int min, int sec, int ms, ZoneId zoneId) { return Date.from( diff --git a/ranger-discovery-bundle/src/test/java/io/appform/ranger/discovery/bundle/id/IdParsersTest.java b/ranger-discovery-bundle/src/test/java/io/appform/ranger/discovery/bundle/id/IdParsersTest.java new file mode 100644 index 00000000..255969e6 --- /dev/null +++ b/ranger-discovery-bundle/src/test/java/io/appform/ranger/discovery/bundle/id/IdParsersTest.java @@ -0,0 +1,46 @@ +package io.appform.ranger.discovery.bundle.id; + +import io.appform.ranger.discovery.bundle.id.formatter.IdFormatters; +import io.appform.ranger.discovery.bundle.id.formatter.IdParsers; +import io.appform.ranger.discovery.bundle.id.generator.DefaultIdGenerator; +import lombok.val; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class IdParsersTest { + + @Test + void testDefaultId() throws ParseException { + val id = "T2407101232336168748798"; + val parsedId = IdParsers.parse(id).orElse(null); + Assertions.assertNotNull(parsedId); + Assertions.assertEquals(id, parsedId.getId()); + Assertions.assertEquals(798, parsedId.getExponent()); + Assertions.assertEquals(8748, parsedId.getNode()); + assertDate("240710123233616", parsedId.getGeneratedDate()); + } + + private void assertDate(final String dateString, final Date date) throws ParseException { + Assertions.assertEquals(new SimpleDateFormat("yyMMddHHmmssSSS").parse(dateString), date); + } + + @Test + void testParseSuccessAfterGenerationWithSuffix() { + val idGenerator = new DefaultIdGenerator(IdFormatters.suffix()); + val prefix = "TEST"; + val suffix = "007"; + val generatedId = idGenerator.generate(prefix, suffix); + val parsedId = IdGenerator.parse(generatedId.getId()).orElse(null); + Assertions.assertNotNull(parsedId); + Assertions.assertEquals(prefix, parsedId.getPrefix()); + Assertions.assertEquals(suffix, parsedId.getSuffix()); + Assertions.assertEquals(parsedId.getId(), generatedId.getId()); + Assertions.assertEquals(parsedId.getExponent(), generatedId.getExponent()); + Assertions.assertEquals(parsedId.getNode(), generatedId.getNode()); + Assertions.assertEquals(parsedId.getGeneratedDate(), generatedId.getGeneratedDate()); + } +}