com.apollographql.apollo.cache.normalized.OptimisticNormalizedCache Maven / Gradle / Ivy
/**
* Copyright 2018-2019 Amazon.com,
* Inc. or its affiliates. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
package com.apollographql.apollo.cache.normalized;
import com.apollographql.apollo.api.internal.Action;
import com.apollographql.apollo.api.internal.Function;
import com.apollographql.apollo.api.internal.Optional;
import com.apollographql.apollo.cache.CacheHeaders;
import com.nytimes.android.external.cache.Cache;
import com.nytimes.android.external.cache.CacheBuilder;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static com.apollographql.apollo.api.internal.Utils.checkNotNull;
public final class OptimisticNormalizedCache extends NormalizedCache {
private final Cache lruCache = CacheBuilder.newBuilder().build();
@Nullable @Override public Record loadRecord(@Nonnull final String key, @Nonnull final CacheHeaders cacheHeaders) {
checkNotNull(key, "key == null");
checkNotNull(cacheHeaders, "cacheHeaders == null");
try {
final Optional nonOptimisticRecord = nextCache()
.flatMap(new Function>() {
@Nonnull @Override public Optional apply(@Nonnull NormalizedCache cache) {
return Optional.fromNullable(cache.loadRecord(key, cacheHeaders));
}
});
final RecordJournal journal = lruCache.getIfPresent(key);
if (journal != null) {
return nonOptimisticRecord.map(new Function() {
@Nonnull @Override public Record apply(@Nonnull Record record) {
Record result = record.clone();
result.mergeWith(journal.snapshot);
return result;
}
}).or(journal.snapshot.clone());
} else {
return nonOptimisticRecord.orNull();
}
} catch (Exception ignore) {
return null;
}
}
@Nonnull @Override public Set merge(@Nonnull final Record record, @Nonnull final CacheHeaders cacheHeaders) {
checkNotNull(record, "record == null");
checkNotNull(cacheHeaders, "cacheHeaders == null");
return nextCache().map(new Function>() {
@Nonnull @Override public Set apply(@Nonnull NormalizedCache cache) {
return cache.merge(record, cacheHeaders);
}
}).or(Collections.emptySet());
}
@Override public void clearAll() {
lruCache.invalidateAll();
//noinspection ResultOfMethodCallIgnored
nextCache().apply(new Action() {
@Override public void apply(@Nonnull NormalizedCache cache) {
cache.clearAll();
}
});
}
@Override public boolean remove(@Nonnull final CacheKey cacheKey) {
checkNotNull(cacheKey, "cacheKey == null");
boolean result = nextCache().map(new Function() {
@Nonnull @Override public Boolean apply(@Nonnull NormalizedCache cache) {
return cache.remove(cacheKey);
}
}).or(Boolean.FALSE);
if (lruCache.getIfPresent(cacheKey.key()) != null) {
lruCache.invalidate(cacheKey.key());
result = true;
}
return result;
}
@Nonnull public Set mergeOptimisticUpdates(@Nonnull Collection recordSet) {
Set aggregatedDependentKeys = new LinkedHashSet<>();
for (Record record : recordSet) {
aggregatedDependentKeys.addAll(mergeOptimisticUpdate(record));
}
return aggregatedDependentKeys;
}
@Nonnull public Set mergeOptimisticUpdate(@Nonnull final Record record) {
checkNotNull(record, "record == null");
final RecordJournal journal = lruCache.getIfPresent(record.key());
if (journal == null) {
lruCache.put(record.key(), new RecordJournal(record));
return Collections.singleton(record.key());
} else {
return journal.commit(record);
}
}
@Nonnull public Set removeOptimisticUpdates(@Nonnull final UUID mutationId) {
checkNotNull(mutationId, "mutationId == null");
Set changedCacheKeys = new HashSet<>();
Set removedKeys = new HashSet<>();
Map recordJournals = lruCache.asMap();
for (Map.Entry entry : recordJournals.entrySet()) {
String cacheKey = entry.getKey();
RecordJournal journal = entry.getValue();
changedCacheKeys.addAll(journal.revert(mutationId));
if (journal.history.isEmpty()) {
removedKeys.add(cacheKey);
}
}
lruCache.invalidateAll(removedKeys);
return changedCacheKeys;
}
private final class RecordJournal {
Record snapshot;
final LinkedList history = new LinkedList<>();
RecordJournal(Record mutationRecord) {
this.snapshot = mutationRecord.clone();
this.history.add(mutationRecord.clone());
}
/**
* Commits new version of record to the history and invalidate snapshot version.
*/
Set commit(Record record) {
history.addLast(record.clone());
return snapshot.mergeWith(record);
}
/**
* Lookups record by mutation id, if it's found removes it from the history and invalidates snapshot record.
* Snapshot record is superposition of all record versions in the history.
*/
Set revert(UUID mutationId) {
int recordIndex = -1;
for (int i = 0; i < history.size(); i++) {
if (mutationId.equals(history.get(i).mutationId())) {
recordIndex = i;
break;
}
}
if (recordIndex == -1) {
return Collections.emptySet();
}
Set changedKeys = new HashSet<>();
changedKeys.add(history.remove(recordIndex).key());
for (int i = Math.max(0, recordIndex - 1); i < history.size(); i++) {
Record record = history.get(i);
if (i == Math.max(0, recordIndex - 1)) {
snapshot = record.clone();
} else {
changedKeys.addAll(snapshot.mergeWith(record));
}
}
return changedKeys;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy