Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.clearfolio.viewer.execution;

import java.util.Objects;
import java.util.UUID;

/**
* Immutable authority token for one conversion-worker claim.
*
* <p>The lease binds a worker to one permanently identified conversion job,
* one positive lifecycle generation, and one unique lease identifier. A later
* publication boundary can therefore reject work produced by an expired or
* superseded claim instead of trusting job identity alone.</p>
*/
public final class ConversionWorkerLease {

private final UUID jobId;
private final long generation;
private final UUID leaseId;

private ConversionWorkerLease(UUID jobId, long generation, UUID leaseId) {
this.jobId = jobId;
this.generation = generation;
this.leaseId = leaseId;
}

/**
* Issues a lease identity after validating all authority components.
*
* @param jobId permanently reserved conversion-job identifier
* @param generation positive lifecycle generation owned by this claim
* @param leaseId unique identifier for this exact worker claim
* @return immutable worker lease
* @throws NullPointerException when {@code jobId} or {@code leaseId} is null
* @throws IllegalArgumentException when {@code generation} is not positive
*/
public static ConversionWorkerLease issue(UUID jobId, long generation, UUID leaseId) {
UUID requiredJobId = Objects.requireNonNull(jobId, "jobId");
UUID requiredLeaseId = Objects.requireNonNull(leaseId, "leaseId");
if (generation <= 0L) {
throw new IllegalArgumentException("generation must be positive");
}
return new ConversionWorkerLease(requiredJobId, generation, requiredLeaseId);
}

/**
* Returns the conversion job whose work this lease may authorize.
*
* @return permanently reserved conversion-job identifier
*/
public UUID jobId() {
return jobId;
}

/**
* Returns the lifecycle generation fenced by this lease.
*
* @return positive lifecycle generation
*/
public long generation() {
return generation;
}

/**
* Returns the unique identifier for this exact worker claim.
*
* @return worker lease identifier
*/
public UUID leaseId() {
return leaseId;
}

/**
* Checks whether candidate publication authority exactly matches this lease.
*
* @param candidateJobId candidate permanently reserved job identifier
* @param candidateGeneration candidate lifecycle generation
* @param candidateLeaseId candidate worker lease identifier
* @return true only when all three authority components exactly match
*/
public boolean authorizes(
UUID candidateJobId,
long candidateGeneration,
UUID candidateLeaseId) {
return jobId.equals(candidateJobId)
&& generation == candidateGeneration
&& leaseId.equals(candidateLeaseId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.clearfolio.viewer.execution;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.UUID;

import org.junit.jupiter.api.Test;

class ConversionWorkerLeaseTest {

@Test
void leaseCarriesImmutableJobGenerationAndLeaseIdentity() {
UUID jobId = UUID.randomUUID();
UUID leaseId = UUID.randomUUID();

ConversionWorkerLease lease = ConversionWorkerLease.issue(jobId, 7L, leaseId);

assertEquals(jobId, lease.jobId());
assertEquals(7L, lease.generation());
assertEquals(leaseId, lease.leaseId());
}

@Test
void authorizationRequiresExactJobGenerationAndLeaseIdentity() {
UUID jobId = UUID.randomUUID();
UUID leaseId = UUID.randomUUID();
ConversionWorkerLease lease = ConversionWorkerLease.issue(jobId, 2L, leaseId);

assertTrue(lease.authorizes(jobId, 2L, leaseId));
assertFalse(lease.authorizes(UUID.randomUUID(), 2L, leaseId));
assertFalse(lease.authorizes(jobId, 3L, leaseId));
assertFalse(lease.authorizes(jobId, 2L, UUID.randomUUID()));
assertFalse(lease.authorizes(null, 2L, leaseId));
assertFalse(lease.authorizes(jobId, 2L, null));
}

@Test
void leaseIssuanceFailsClosedForMissingOrNonPositiveAuthority() {
UUID jobId = UUID.randomUUID();
UUID leaseId = UUID.randomUUID();

assertThrows(NullPointerException.class, () -> ConversionWorkerLease.issue(null, 1L, leaseId));
assertThrows(NullPointerException.class, () -> ConversionWorkerLease.issue(jobId, 1L, null));
assertThrows(IllegalArgumentException.class, () -> ConversionWorkerLease.issue(jobId, 0L, leaseId));
assertThrows(IllegalArgumentException.class, () -> ConversionWorkerLease.issue(jobId, -1L, leaseId));
}
}
Loading