Skip to content
Merged
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
25 changes: 24 additions & 1 deletion core/src/main/java/net/staticstudios/data/CachedValue.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package net.staticstudios.data;

import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import net.staticstudios.data.impl.data.AbstractCachedValue;
import net.staticstudios.data.util.*;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;

/**
* A cached value represents a piece of data in redis.
Expand All @@ -30,14 +30,19 @@ default CachedValue<T> withFallback(T fallback) {
return supplyFallback(() -> fallback);
}

<U extends UniqueData> CachedValue<T> refresher(Class<U> clazz, CachedValueRefresher<U, T> refresher);

CachedValue<T> supplyFallback(Supplier<T> fallback);

@Nullable T refresh();

class ProxyCachedValue<T> implements CachedValue<T> {
protected final UniqueData holder;
protected final Class<T> dataType;
private final List<ValueUpdateHandlerWrapper<?, T>> updateHandlers = new ArrayList<>();
private @Nullable CachedValue<T> delegate;
private Supplier<T> fallback = () -> null;
private @Nullable CachedValueRefresher<UniqueData, T> refresher;

public ProxyCachedValue(UniqueData holder, Class<T> dataType) {
this.holder = holder;
Expand All @@ -48,6 +53,7 @@ public void setDelegate(CachedValueMetadata metadata, AbstractCachedValue<T> del
Preconditions.checkNotNull(delegate, "Delegate cannot be null");
Preconditions.checkState(this.delegate == null, "Delegate is already set");
delegate.setFallback(this.fallback);
delegate.setRefresher(refresher);
this.delegate = delegate;

//since an update handler can be registered before the fallback is set, we need to convert them here
Expand Down Expand Up @@ -88,6 +94,15 @@ public CachedValue<T> supplyFallback(Supplier<T> fallback) {
return this;
}

@SuppressWarnings("unchecked")
@Override
public <U extends UniqueData> CachedValue<T> refresher(Class<U> clazz, CachedValueRefresher<U, T> refresher) {
Preconditions.checkArgument(delegate == null, "Cannot dynamically add a refresher after the holder has been initialized!");
LambdaUtils.assertLambdaDoesntCapture(refresher, List.of(UniqueData.class), null);
this.refresher = (CachedValueRefresher<UniqueData, T>) refresher;
return this;
}

@Override
public T get() {
if (delegate != null) {
Expand All @@ -105,6 +120,14 @@ public void set(@Nullable T value) {
throw new UnsupportedOperationException("Not implemented");
}

@Override
public @Nullable T refresh() {
if (delegate != null) {
return delegate.refresh();
}
throw new UnsupportedOperationException("Not implemented");
}

private <U extends UniqueData> CachedValueUpdateHandlerWrapper<U, T> asCachedValueHandler(ValueUpdateHandlerWrapper<U, T> handlerWrapper) {
return new CachedValueUpdateHandlerWrapper<>(
handlerWrapper.getHandler(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
package net.staticstudios.data.impl.data;

import net.staticstudios.data.CachedValue;
import net.staticstudios.data.UniqueData;
import net.staticstudios.data.util.CachedValueRefresher;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;

import java.util.function.Supplier;

public abstract class AbstractCachedValue<T> implements CachedValue<T> {
private Supplier<T> fallbackSupplier;
private CachedValueRefresher<UniqueData, T> refreshFunction;

@ApiStatus.Internal
public void setFallback(Supplier<T> fallbackSupplier) {
this.fallbackSupplier = fallbackSupplier;
}

@ApiStatus.Internal
public void setRefresher(CachedValueRefresher<UniqueData, T> refreshFunction) {
this.refreshFunction = refreshFunction;
}

protected T getFallback() {
if (fallbackSupplier != null) {
return fallbackSupplier.get();
}
return null;
}

protected T calculateRefreshedValue(@Nullable T currentValue) {
if (refreshFunction != null) {
return refreshFunction.apply(getHolder(), currentValue);
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package net.staticstudios.data.impl.data;

import com.google.common.base.Preconditions;
import com.google.common.base.Supplier;
import net.staticstudios.data.CachedValue;
import net.staticstudios.data.ExpireAfter;
import net.staticstudios.data.Identifier;
Expand All @@ -13,6 +12,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

public class CachedValueImpl<T> extends AbstractCachedValue<T> {
private final UniqueData holder;
Expand Down Expand Up @@ -95,6 +95,11 @@ public <U extends UniqueData> CachedValue<T> onUpdate(Class<U> holderClass, Valu
throw new UnsupportedOperationException("Dynamically adding update handlers is not supported");
}

@Override
public <U extends UniqueData> CachedValue<T> refresher(Class<U> clazz, CachedValueRefresher<U, T> refresher) {
throw new UnsupportedOperationException("Cannot set refresher after initialization");
}

@Override
public CachedValue<T> supplyFallback(Supplier<T> fallback) {
throw new UnsupportedOperationException("Cannot set fallback after initialization");
Expand All @@ -105,6 +110,12 @@ public T get() {
Preconditions.checkArgument(!holder.isDeleted(), "Cannot get value from a deleted UniqueData instance");
T value = holder.getDataManager().getRedis(metadata.holderSchema(), metadata.holderTable(), metadata.identifier(), holder.getIdColumns(), dataType);
if (value == null) {
T refreshed = calculateRefreshedValue(getFallback());
if (refreshed != null) {
set(refreshed);
return refreshed;
}

return getFallback();
}
return value;
Expand All @@ -123,6 +134,18 @@ public void set(@Nullable T value) {
holder.getDataManager().setRedis(metadata.holderSchema(), metadata.holderTable(), metadata.identifier(), holder.getIdColumns(), metadata.expireAfterSeconds(), toSet);
}

@Override
public @Nullable T refresh() {
Preconditions.checkArgument(!holder.isDeleted(), "Cannot refresh value on a deleted UniqueData instance");
T value = holder.getDataManager().getRedis(metadata.holderSchema(), metadata.holderTable(), metadata.identifier(), holder.getIdColumns(), dataType);
if (value == null) {
value = getFallback();
}
T refreshed = calculateRefreshedValue(value);
set(refreshed);
return refreshed;
}

@Override
public String toString() {
if (holder.isDeleted()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package net.staticstudios.data.impl.data;

import com.google.common.base.Supplier;
import net.staticstudios.data.CachedValue;
import net.staticstudios.data.UniqueData;
import net.staticstudios.data.util.*;
import org.jetbrains.annotations.Nullable;

import java.util.function.Supplier;

public class ReadOnlyCachedValue<T> extends AbstractCachedValue<T> {
private final T value;
private final UniqueData holder;
Expand Down Expand Up @@ -68,6 +69,11 @@ public CachedValue<T> supplyFallback(Supplier<T> fallback) {
throw new UnsupportedOperationException("Cannot set fallback on a read-only CachedValue");
}

@Override
public <U extends UniqueData> CachedValue<T> refresher(Class<U> clazz, CachedValueRefresher<U, T> refresher) {
throw new UnsupportedOperationException("Cannot set refresher on a read-only CachedValue");
}

@Override
public T get() {
return value;
Expand All @@ -78,6 +84,11 @@ public void set(T value) {
throw new UnsupportedOperationException("Cannot set value on a read-only CachedValue");
}

@Override
public @Nullable T refresh() {
return value;
}

@Override
public String toString() {
return "ReadOnlyCachedValue{" +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.staticstudios.data.util;

import net.staticstudios.data.UniqueData;
import org.jetbrains.annotations.Nullable;

public interface CachedValueRefresher<U extends UniqueData, T> {
T apply(U holder, @Nullable T previousValue);
}
31 changes: 29 additions & 2 deletions core/src/test/java/net/staticstudios/data/CachedValueTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@

import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.*;

public class CachedValueTest extends DataTest {

Expand Down Expand Up @@ -159,4 +158,32 @@ public void testLoadCachedValues() {
assertEquals(true, user.onCooldown.get());
assertEquals(5, user.cooldownUpdates.get());
}

@Test
public void testRefreshCachedValues() throws InterruptedException {
DataManager dataManager = getMockEnvironments().getFirst().dataManager();
dataManager.load(MockUser.class);
dataManager.finishLoading();
MockUser user = MockUser.builder(dataManager)
.id(UUID.randomUUID())
.name("john doe")
.insert(InsertMode.ASYNC);


assertEquals(0, user.counter.get());
assertEquals(1, user.counter.refresh());
assertEquals(2, user.counter.refresh());
assertEquals(2, user.counter.get());

Thread.sleep(10_000); //wait for the cached value to expire

String counterKey = RedisUtils.buildRedisKey("public", "users", "counter", user.getIdColumns());

Jedis jedis = getJedis();
assertFalse(jedis.exists(counterKey));

assertEquals(2, user.counter.get()); //trigger a refresh
waitForDataPropagation();
assertEquals("2", gson.fromJson(jedis.get(counterKey), RedisEncodedValue.class).value());
}
}
14 changes: 14 additions & 0 deletions core/src/test/java/net/staticstudios/data/mock/user/MockUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ public class MockUser extends UniqueData {
.onUpdate(MockUser.class, (user, update) -> user.cooldownUpdates.set(user.cooldownUpdates.get() + 1))
.withFallback(false);

@Column(name = "counter", nullable = true)
public PersistentValue<Integer> persistentCounter;

@ExpireAfter(5)
@Identifier("counter")
public CachedValue<Integer> counter = CachedValue.of(this, Integer.class)
.refresher(MockUser.class, (user, prev) -> {
if (prev == null) {
return user.persistentCounter.get() != null ? user.persistentCounter.get() : 0;
}
return prev + 1;
})
.onUpdate(MockUser.class, (user, update) -> user.persistentCounter.set(update.newValue()));

public int getNameUpdates() {
return nameUpdates.get();
}
Expand Down
Loading