How can I store a value until an expiration timestamp in Java?

Viewed 352

I have a value that is known to be valid until a certain timestamp, at which point it expires and should no longer be used. This is in a Java Spring Boot app using the Google Guava library.

I've implemented this using a simple wrapper with an expiry timestamp.

import java.time.Clock;
import java.time.Instant;
import java.util.Objects;
import java.util.Optional;

public class ExpiringValue<T> {
    private final T value;

    private final Instant expiresAt;

    private final Clock clock;

    public ExpiringValue(T value, Instant expiresAt) {
        this(value, expiresAt, Clock.systemDefaultZone());
    }

    public ExpiringValue(T value, Instant expiresAt, Clock clock) {
        this.value = Objects.requireNonNull(value);
        this.expiresAt = Objects.requireNonNull(expiresAt);
        this.clock = Objects.requireNonNull(clock);
    }

    public Optional<T> get() {
        return isExpired() ? Optional.empty() : Optional.ofNullable(value);
    }

    private boolean isExpired() {
        return !Instant.now(clock).isBefore(expiresAt);
    }
}
private static void testExpires(Object value, Instant expiresAt) {
    ExpiringValue<Object> expiringValue = new ExpiringValue<>(value, expiresAt);

    // Time passes...

    expiringValue.get().orElseThrow(() -> new RuntimeException("Value is no longer valid"));
}

That said, this feels like common enough functionality that I'm probably re-inventing the wheel by doing so. I've found Guava's Suppliers.memoizeWithExpiration. However, this is more designed around the actual function that generates the value, so it would require some hoops to make it work with an already existing value. It also doesn't allow passing in a Clock, meaning that higher-level automated tests for code using the functionality cannot just manually update the clock to verify that expiration is happening as expected.

private static void testExpires(Object value, Instant expiresAt) {
    Instant now = Instant.now();
    if (!expiresAt.isAfter(now)) {
        throw new RuntimeException("Value is no longer valid");
    }
    AtomicBoolean supplied = new AtomicBoolean(false);
    Supplier<Object> expiringValue = Suppliers.memoizeWithExpiration(() -> {
                if (supplied.getAndSet(true)) {
                    throw new RuntimeException("Value is no longer valid");
                } else {
                    return value;
                }
    }, Duration.between(now, expiresAt).toNanos(), TimeUnit.NANOSECONDS);
    expiringValue.get(); // Start the countdown

    // Time passes...

    expiringValue.get();
}

Is there a more straightforward way to accomplish this goal than implementing it myself?

0 Answers
Related