Skip to content

Commit

Permalink
Merge pull request #43
Browse files Browse the repository at this point in the history
Extract out IdGeneratorBase and Add NonceGenerator
  • Loading branch information
santanusinha authored Feb 19, 2025
2 parents 0411e60 + eca0d8d commit 7da19f9
Show file tree
Hide file tree
Showing 17 changed files with 475 additions and 219 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
<maven.compiler.version>3.8.0</maven.compiler.version>
<java.version>17</java.version>
<java.release.version>17</java.release.version>
<lombok.version>1.18.22</lombok.version>
<lombok.version>1.18.30</lombok.version>
<annotations.version>3.0.1u2</annotations.version>

<junit.version>5.8.2</junit.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"iterations" : 4,
"threads" : 1,
"forks" : 3,
"mean_ops" : 745876.9926227567
"mean_ops" : 823635.7718335792
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
"iterations" : 4,
"threads" : 1,
"forks" : 3,
"mean_ops" : 578009.6941965051
"mean_ops" : 655010.8023043653
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.appform.ranger.discovery.bundle.id;

import lombok.Getter;
import lombok.Value;

@Getter
@Value
public class GenerationResult {
NonceInfo nonceInfo;
IdValidationState state;
Domain domain;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,108 +16,57 @@

package io.appform.ranger.discovery.bundle.id;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import dev.failsafe.Failsafe;
import dev.failsafe.FailsafeExecutor;
import dev.failsafe.RetryPolicy;
import io.appform.ranger.discovery.bundle.id.constraints.IdValidationConstraint;
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.formatter.IdParsers;
import io.appform.ranger.discovery.bundle.id.generator.DefaultIdGenerator;
import io.appform.ranger.discovery.bundle.id.generator.IdGeneratorBase;
import io.appform.ranger.discovery.bundle.id.request.IdGenerationRequest;
import lombok.NonNull;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.security.SecureRandom;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
* Id generation
*/
@SuppressWarnings("unused")
@Slf4j
public class IdGenerator {

private static final int MINIMUM_ID_LENGTH = 22;
private static final SecureRandom SECURE_RANDOM = new SecureRandom(Long.toBinaryString(System.currentTimeMillis())
.getBytes());
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyMMddHHmmssSSS");

private static final Map<String, Domain> REGISTERED_DOMAINS =
new ConcurrentHashMap<>(Map.of(Domain.DEFAULT_DOMAIN_NAME,
Domain.DEFAULT));
private static final RetryPolicy<GenerationResult> RETRY_POLICY = RetryPolicy.<GenerationResult>builder()
.withMaxAttempts(readRetryCount())
.handleIf(throwable -> true)
.handleResultIf(Objects::isNull)
.handleResultIf(generationResult -> generationResult.getState() == IdValidationState.INVALID_RETRYABLE)
.onRetry(event -> {
val res = event.getLastResult();
if (null != res && !res.getState().equals(IdValidationState.VALID)) {
val id = res.getId();
val collisionChecker = Strings.isNullOrEmpty(res.getDomain())
? Domain.DEFAULT.getCollisionChecker()
: REGISTERED_DOMAINS.get(res.getDomain()).getCollisionChecker();
collisionChecker.free(id.getGeneratedDate().getTime(), id.getExponent());
}
})
.build();
private static final FailsafeExecutor<GenerationResult> RETRIER
= Failsafe.with(Collections.singletonList(RETRY_POLICY));
private static final Pattern PATTERN = Pattern.compile("(.*)([0-9]{15})([0-9]{4})([0-9]{3})");

private static final List<IdValidationConstraint> GLOBAL_CONSTRAINTS = new ArrayList<>();
private static int nodeId;
private static final IdGeneratorBase baseGenerator = new DefaultIdGenerator();

public static void initialize(int node) {
nodeId = node;
baseGenerator.setNodeId(node);
}

public static synchronized void cleanUp() {
GLOBAL_CONSTRAINTS.clear();
REGISTERED_DOMAINS.clear();
baseGenerator.cleanUp();
}

public static synchronized void initialize(
int node, List<IdValidationConstraint> globalConstraints,
Map<String, List<IdValidationConstraint>> domainSpecificConstraints) {
nodeId = node;
if (null != globalConstraints) {
IdGenerator.GLOBAL_CONSTRAINTS.addAll(globalConstraints);
initialize(node);
if(null != globalConstraints && !globalConstraints.isEmpty() ) {
baseGenerator.registerGlobalConstraints(globalConstraints);
}

if (null != domainSpecificConstraints) {
domainSpecificConstraints
.forEach((domain, constraints) -> REGISTERED_DOMAINS.put(domain, Domain.builder()
.domain(domain)
.constraints(Objects.requireNonNullElse(constraints, List.<IdValidationConstraint>of()))
.idFormatter(IdFormatters.original())
.resolution(TimeUnit.MILLISECONDS)
.build()));
domainSpecificConstraints.forEach(baseGenerator::registerDomainSpecificConstraints);
}
}

public static void registerDomain(Domain domain) {
REGISTERED_DOMAINS.put(domain.getDomain(), domain);
baseGenerator.registerDomain(domain);
}


public static synchronized void registerGlobalConstraints(IdValidationConstraint... constraints) {
registerGlobalConstraints(ImmutableList.copyOf(constraints));
}

public static synchronized void registerGlobalConstraints(List<IdValidationConstraint> constraints) {
Preconditions.checkArgument(null != constraints && !constraints.isEmpty());
GLOBAL_CONSTRAINTS.addAll(constraints);
baseGenerator.registerGlobalConstraints(constraints);
}

public static synchronized void registerDomainSpecificConstraints(
Expand All @@ -129,45 +78,23 @@ public static synchronized void registerDomainSpecificConstraints(
public static synchronized void registerDomainSpecificConstraints(
String domain,
List<IdValidationConstraint> validationConstraints) {
Preconditions.checkArgument(null != validationConstraints && !validationConstraints.isEmpty());
REGISTERED_DOMAINS.computeIfAbsent(domain, key -> Domain.builder()
.domain(domain)
.constraints(validationConstraints)
.idFormatter(IdFormatters.original())
.resolution(TimeUnit.MILLISECONDS)
.build());
baseGenerator.registerDomainSpecificConstraints(domain, validationConstraints);
}


/**
* Generate id with given prefix
*
* @param prefix String prefix with will be used to blindly merge
* @return Generated Id
*/
public static Id generate(String prefix) {
return generate(prefix, IdFormatters.original(), Domain.DEFAULT.getCollisionChecker());
return baseGenerator.generate(prefix);
}

public static Id generate(
final String prefix,
final IdFormatter idFormatter) {
return generate(prefix, idFormatter, Domain.DEFAULT.getCollisionChecker());
}

private static Id generate(
final String prefix,
final IdFormatter idFormatter,
final CollisionChecker collisionChecker) {
val idInfo = random(collisionChecker);
val dateTime = new DateTime(idInfo.time);
val id = String.format("%s%s", prefix, idFormatter.format(dateTime, nodeId, idInfo.exponent));
return Id.builder()
.id(id)
.exponent(idInfo.exponent)
.generatedDate(dateTime.toDate())
.node(nodeId)
.build();
return baseGenerator.generate(prefix, idFormatter);
}

/**
Expand All @@ -194,11 +121,11 @@ public static Optional<Id> generateWithConstraints(String prefix, @NonNull Strin
* @return Id if it could be generated
*/
public static Optional<Id> generateWithConstraints(String prefix, @NonNull String domain, boolean skipGlobal) {
return generateWithConstraints(prefix, REGISTERED_DOMAINS.getOrDefault(domain, Domain.DEFAULT), skipGlobal);
return baseGenerator.generateWithConstraints(prefix, domain, skipGlobal);
}

/**
* Generate id that mathces all passed constraints.
* Generate id that matches all passed constraints.
* NOTE: There are performance implications for this.
* The evaluation of constraints will take it's toll on id generation rates. Tun rests to check speed.
*
Expand All @@ -219,30 +146,11 @@ public static Optional<Id> generateWithConstraints(
* @return Id if it could be generated
*/
public static Optional<Id> parse(final String idString) {
if (idString == null
|| idString.length() < MINIMUM_ID_LENGTH) {
return Optional.empty();
}
try {
val matcher = PATTERN.matcher(idString);
if (matcher.find()) {
return Optional.of(Id.builder()
.id(idString)
.node(Integer.parseInt(matcher.group(3)))
.exponent(Integer.parseInt(matcher.group(4)))
.generatedDate(DATE_TIME_FORMATTER.parseDateTime(matcher.group(2)).toDate())
.build());
}
return Optional.empty();
}
catch (Exception e) {
log.warn("Could not parse idString {}", e.getMessage());
return Optional.empty();
}
return IdParsers.parse(idString);
}

/**
* Generate id that mathces all passed constraints.
* Generate id that matches all passed constraints.
* NOTE: There are performance implications for this.
* The evaluation of constraints will take it's toll on id generation rates. Tun rests to check speed.
*
Expand All @@ -264,7 +172,7 @@ public static Optional<Id> generateWithConstraints(
}

/**
* Generate id that mathces all passed constraints.
* Generate id that matches all passed constraints.
* NOTE: There are performance implications for this.
* The evaluation of constraints will take it's toll on id generation rates. Tun rests to check speed.
*
Expand All @@ -287,99 +195,7 @@ private static Optional<Id> generateWithConstraints(
}

public static Optional<Id> generate(final IdGenerationRequest request) {
return Optional.ofNullable(RETRIER.get(
() -> {
Id id = generate(request.getPrefix(), request.getIdFormatter(),
!Strings.isNullOrEmpty(request.getDomain())
? REGISTERED_DOMAINS.getOrDefault(request.getDomain(), Domain.DEFAULT)
.getCollisionChecker()
: Domain.DEFAULT.getCollisionChecker());
return new GenerationResult(id,
validateId(request.getConstraints(),
id,
request.isSkipGlobal()),
request.getDomain());
}))
.filter(generationResult -> generationResult.getState() == IdValidationState.VALID)
.map(GenerationResult::getId);
}

private static IdInfo random(CollisionChecker collisionChecker) {
int randomGen;
long curTimeMs;
do {
curTimeMs = System.currentTimeMillis();
randomGen = SECURE_RANDOM.nextInt(Constants.MAX_ID_PER_MS);
} while (!collisionChecker.check(curTimeMs, randomGen));
return new IdInfo(randomGen, curTimeMs);
}

private static IdValidationState validateId(List<IdValidationConstraint> inConstraints, Id id, boolean skipGlobal) {
//First evaluate global constraints
val failedGlobalConstraint
= skipGlobal
? null
: GLOBAL_CONSTRAINTS.stream()
.filter(constraint -> !constraint.isValid(id))
.findFirst()
.orElse(null);
if (null != failedGlobalConstraint) {
return failedGlobalConstraint.failFast()
? IdValidationState.INVALID_NON_RETRYABLE
: IdValidationState.INVALID_RETRYABLE;
}
//Evaluate local + domain constraints
val failedLocalConstraint
= null == inConstraints
? null
: inConstraints.stream()
.filter(constraint -> !constraint.isValid(id))
.findFirst()
.orElse(null);
if (null != failedLocalConstraint) {
return failedLocalConstraint.failFast()
? IdValidationState.INVALID_NON_RETRYABLE
: IdValidationState.INVALID_RETRYABLE;
}
return IdValidationState.VALID;
}

private static int readRetryCount() {
try {
val count = Integer.parseInt(System.getenv().getOrDefault("NUM_ID_GENERATION_RETRIES", "512"));
if (count <= 0) {
throw new IllegalArgumentException(
"Negative number of retries does not make sense. Please set a proper value for " +
"NUM_ID_GENERATION_RETRIES");
}
return count;
}
catch (NumberFormatException e) {
throw new IllegalArgumentException("Please provide a valid positive integer for NUM_ID_GENERATION_RETRIES");
}
}

private enum IdValidationState {
VALID,
INVALID_RETRYABLE,
INVALID_NON_RETRYABLE
}

@Value
private static class IdInfo {
int exponent;
long time;

public IdInfo(int exponent, long time) {
this.exponent = exponent;
this.time = time;
}
return baseGenerator.generateWithConstraints(request);
}

@Value
private static class GenerationResult {
Id id;
IdValidationState state;
String domain;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.appform.ranger.discovery.bundle.id;

public enum IdValidationState {
VALID,
INVALID_RETRYABLE,
INVALID_NON_RETRYABLE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.appform.ranger.discovery.bundle.id;

import lombok.Value;

@Value
public class NonceInfo {

int exponent;
long time;

public NonceInfo(int exponent, long time) {
this.exponent = exponent;
this.time = time;
}
}
Loading

0 comments on commit 7da19f9

Please sign in to comment.