org.babyfish.jimmer.sql.cache.chain.ChainCacheImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jimmer-sql Show documentation
Show all versions of jimmer-sql Show documentation
A revolutionary ORM framework for both java and kotlin
The newest version!
package org.babyfish.jimmer.sql.cache.chain;
import org.babyfish.jimmer.meta.ImmutableProp;
import org.babyfish.jimmer.meta.ImmutableType;
import org.babyfish.jimmer.sql.cache.Cache;
import org.babyfish.jimmer.sql.cache.CacheEnvironment;
import org.babyfish.jimmer.sql.cache.CacheLoader;
import org.babyfish.jimmer.sql.exception.ExecutionException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.Supplier;
class ChainCacheImpl implements Cache {
private static final ThreadLocal> LOADER_LOCAL =
new ThreadLocal<>();
protected final ImmutableType type;
protected final ImmutableProp prop;
protected final Node node;
@SuppressWarnings("unchecked")
public ChainCacheImpl(List> binders) {
if (binders.isEmpty()) {
throw new IllegalArgumentException("binders cannot be empty");
}
ImmutableType cacheType = null;
ImmutableProp cacheProp = null;
Node node = this.createTailNode();
ListIterator> itr = binders.listIterator(binders.size());
while (itr.hasPrevious()) {
Binder binder = itr.previous();
ImmutableType type = binder.type();
ImmutableProp prop = binder.prop();
if (cacheType == null) {
cacheType = type;
cacheProp = prop;
} else {
if (cacheType != type || cacheProp != prop) {
throw new IllegalArgumentException(
"Not all binders belong to same type/prop"
);
}
}
node = createNode(binder, node);
}
this.type = cacheType;
this.prop = cacheProp;
this.node = node;
}
@Override
public @NotNull ImmutableType type() {
return type;
}
@Override
public @Nullable ImmutableProp prop() {
return prop;
}
@NotNull
@Override
public Map getAll(@NotNull Collection keys, @NotNull CacheEnvironment env) {
return usingCacheLoading(env.getLoader(), () -> node.loadAll(keys));
}
@Override
public void deleteAll(@NotNull Collection keys, Object reason) {
node.deleteAll(keys, reason);
}
@SuppressWarnings("unchecked")
protected Node createNode(Object binder, Node next) {
if (binder instanceof LoadingBinder, ?>) {
return new LoadingNode<>((LoadingBinder) binder, next);
}
return new SimpleNode<>((SimpleBinder) binder, next);
}
protected TailNode createTailNode() {
return new TailNode<>();
}
protected interface Node extends CacheChain {
void deleteAll(@NotNull Collection keys, Object reason);
}
private static class LoadingNode implements Node {
private final LoadingBinder binder;
private final Node next;
LoadingNode(LoadingBinder binder, Node next) {
this.binder = binder;
this.next = next;
binder.initialize(next);
}
@NotNull
@Override
public Map loadAll(@NotNull Collection keys) {
return binder.getAll(keys);
}
@Override
public void deleteAll(@NotNull Collection keys, Object reason) {
next.deleteAll(keys, reason);
binder.deleteAll(keys, reason);
}
}
protected static class SimpleNode implements Node {
protected final SimpleBinder binder;
protected final Node next;
protected SimpleNode(SimpleBinder binder, Node next) {
this.binder = binder;
this.next = next;
}
@NotNull
@Override
public Map loadAll(@NotNull Collection keys) {
Map map = binder.getAll(keys);
if (map.size() < keys.size()) {
if (binder instanceof LockedBinder, ?>) {
LockedBinder, ?> lockedBinder = (LockedBinder, ?>) binder;
Set missedKeys = missedKeys(keys, map);
try {
lockedBinder.locker().locking(
lockedBinder.unwrap(),
missedKeys,
lockedBinder.waitDuration(),
lockedBinder.leaseDuration(),
locked -> {
loadAllForNext(missedKeys, map, locked);
}
);
} catch (ExecutionException ex) {
throw ex;
} catch (Exception ex) {
throw new ExecutionException(
"Failed to load missed data and update cache",
ex
);
}
} else {
loadAllForNext(missedKeys(keys, map), map, true);
}
}
return map;
}
@Override
public void deleteAll(@NotNull Collection keys, Object reason) {
if (keys.isEmpty()) {
return;
}
if (binder instanceof LockedBinder, ?>) {
LockedBinder, ?> lockedBinder = (LockedBinder, ?>) binder;
try {
lockedBinder.locker().locking(
lockedBinder.unwrap(),
keys instanceof Set> ?
(Set) keys :
new LinkedHashSet<>(keys),
null,
lockedBinder.leaseDuration(),
locked -> {
next.deleteAll(keys, reason);
binder.deleteAll(keys, reason);
}
);
} catch (ExecutionException ex) {
throw ex;
} catch (Exception ex) {
throw new ExecutionException(
"Failed to delete keys from cache",
ex
);
}
} else {
next.deleteAll(keys, reason);
binder.deleteAll(keys, reason);
}
}
private static Set missedKeys(Collection keys, Map loadedMap) {
Set missedKeys = new LinkedHashSet<>();
for (K key : keys) {
if (!loadedMap.containsKey(key)) {
missedKeys.add(key);
}
}
return missedKeys;
}
private void loadAllForNext(
Collection missedKeys,
Map loadedMap,
boolean updateBinder
) {
Map mapFromNext = next.loadAll(missedKeys);
if (mapFromNext.size() < missedKeys.size()) {
mapFromNext = new HashMap<>(mapFromNext);
if (updateBinder) {
for (K missedKey : missedKeys) {
if (!mapFromNext.containsKey(missedKey)) {
mapFromNext.put(missedKey, null);
}
}
}
}
if (updateBinder) {
binder.setAll(mapFromNext);
}
loadedMap.putAll(mapFromNext);
}
}
protected static class TailNode implements Node {
@NotNull
@Override
public Map loadAll(@NotNull Collection keys) {
CacheLoader loader = currentCacheLoader();
return loader.loadAll(keys);
}
@Override
public void deleteAll(@NotNull Collection keys, Object reason) {
}
}
protected static R usingCacheLoading(
CacheLoader, ?> loader,
Supplier block
) {
if (loader == null) {
throw new IllegalArgumentException("loader cannot be null");
}
CacheLoader, ?> oldLoader = LOADER_LOCAL.get();
LOADER_LOCAL.set(loader);
try {
return block.get();
} finally {
if (oldLoader != null) {
LOADER_LOCAL.set(oldLoader);
} else {
LOADER_LOCAL.remove();
}
}
}
@SuppressWarnings("unchecked")
protected static CacheLoader currentCacheLoader() {
CacheLoader, ?> loader = LOADER_LOCAL.get();
if (loader == null) {
throw new IllegalStateException(
"Cache binder can only be called by chain cache"
);
}
return (CacheLoader) loader;
}
}