package com.apollographql.apollo.internal.cache.normalized;
import com.apollographql.apollo.CustomTypeAdapter;
import com.apollographql.apollo.api.Field;
import com.apollographql.apollo.api.GraphqlFragment;
import com.apollographql.apollo.api.Operation;
import com.apollographql.apollo.api.Response;
import com.apollographql.apollo.api.ResponseFieldMapper;
import com.apollographql.apollo.api.ScalarType;
import com.apollographql.apollo.cache.CacheHeaders;
import com.apollographql.apollo.cache.normalized.ApolloStore;
import com.apollographql.apollo.cache.normalized.CacheKey;
import com.apollographql.apollo.cache.normalized.CacheKeyResolver;
import com.apollographql.apollo.cache.normalized.NormalizedCache;
import com.apollographql.apollo.cache.normalized.Record;
import com.apollographql.apollo.internal.field.CacheFieldValueResolver;
import com.apollographql.apollo.internal.reader.RealResponseReader;
import com.apollographql.apollo.internal.util.ApolloLogger;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import static com.apollographql.apollo.api.internal.Utils.checkNotNull;
public final class RealApolloStore implements ApolloStore, ReadableStore, WriteableStore {
private final NormalizedCache normalizedCache;
private final CacheKeyResolver cacheKeyResolver;
private final Map<ScalarType, CustomTypeAdapter> customTypeAdapters;
private final ApolloLogger logger;
private final ReadWriteLock lock;
private final Set<RecordChangeSubscriber> subscribers;
public RealApolloStore(@Nonnull NormalizedCache normalizedCache, @Nonnull CacheKeyResolver cacheKeyResolver,
@Nonnull final Map<ScalarType, CustomTypeAdapter> customTypeAdapters, @Nonnull ApolloLogger logger) {
this.normalizedCache = checkNotNull(normalizedCache, "cacheStore == null");
this.cacheKeyResolver = checkNotNull(cacheKeyResolver, "cacheKeyResolver == null");
this.customTypeAdapters = checkNotNull(customTypeAdapters, "customTypeAdapters == null");
this.logger = checkNotNull(logger, "logger == null");
this.lock = new ReentrantReadWriteLock();
this.subscribers = Collections.newSetFromMap(new WeakHashMap<RecordChangeSubscriber, Boolean>());
}
@Override public ResponseNormalizer<Map<String, Object>> networkResponseNormalizer() {
return new ResponseNormalizer<Map<String, Object>>() {
@Nonnull @Override public CacheKey resolveCacheKey(@Nonnull Field field, @Nonnull Map<String, Object> record) {
return cacheKeyResolver.fromFieldRecordSet(field, record);
}
};
}
@Override public ResponseNormalizer<Record> cacheResponseNormalizer() {
return new ResponseNormalizer<Record>() {
@Nonnull @Override public CacheKey resolveCacheKey(@Nonnull Field field, @Nonnull Record record) {
return CacheKey.from(record.key());
}
};
}
@Override public synchronized void subscribe(RecordChangeSubscriber subscriber) {
subscribers.add(subscriber);
}
@Override public synchronized void unsubscribe(RecordChangeSubscriber subscriber) {
subscribers.remove(subscriber);
}
@Override public void publish(@Nonnull Set<String> changedKeys) {
checkNotNull(changedKeys, "changedKeys == null");
if (changedKeys.isEmpty()) {
return;
}
Set<RecordChangeSubscriber> iterableSubscribers;
synchronized (this) {
iterableSubscribers = new LinkedHashSet<>(subscribers);
}
for (RecordChangeSubscriber subscriber : iterableSubscribers) {
subscriber.onCacheRecordsChanged(changedKeys);
}
}
@Override public void clearAll() {
writeTransaction(new Transaction<WriteableStore, Boolean>() {
@Override public Boolean execute(WriteableStore cache) {
cache.clearAll();
return true;
}
});
}
@Override public <R> R readTransaction(Transaction<ReadableStore, R> transaction) {
lock.readLock().lock();
try {
return transaction.execute(RealApolloStore.this);
} finally {
lock.readLock().unlock();
}
}
@Override public <R> R writeTransaction(Transaction<WriteableStore, R> transaction) {
lock.writeLock().lock();
try {
return transaction.execute(RealApolloStore.this);
} finally {
lock.writeLock().unlock();
}
}
@Override public NormalizedCache normalizedCache() {
return normalizedCache;
}
@Nullable public Record read(@Nonnull String key, @Nonnull CacheHeaders cacheHeaders) {
return normalizedCache.loadRecord(checkNotNull(key, "key == null"), cacheHeaders);
}
@Nonnull public Collection<Record> read(@Nonnull Collection<String> keys, @Nonnull CacheHeaders cacheHeaders) {
return normalizedCache.loadRecords(checkNotNull(keys, "keys == null"), cacheHeaders);
}
@Nonnull public Set<String> merge(@Nonnull Collection<Record> recordSet, @Nonnull CacheHeaders cacheHeaders) {
return normalizedCache.merge(checkNotNull(recordSet, "recordSet == null"), cacheHeaders);
}
@Override public CacheKeyResolver cacheKeyResolver() {
return cacheKeyResolver;
}
@Nullable @Override public <D extends Operation.Data, T, V extends Operation.Variables> T read(
@Nonnull final Operation<D, T, V> operation) {
checkNotNull(operation, "operation == null");
return readTransaction(new Transaction<ReadableStore, T>() {
@Nullable @Override public T execute(ReadableStore cache) {
Record rootRecord = cache.read(CacheKeyResolver.rootKeyForOperation(operation).key(), CacheHeaders.NONE);
if (rootRecord == null) {
return null;
}
ResponseFieldMapper<D> responseFieldMapper = operation.responseFieldMapper();
CacheFieldValueResolver fieldValueResolver = new CacheFieldValueResolver(cache, operation.variables(),
cacheKeyResolver(), CacheHeaders.NONE);
//noinspection unchecked
RealResponseReader<Record> responseReader = new RealResponseReader<>(operation.variables(), rootRecord,
fieldValueResolver, customTypeAdapters, ResponseNormalizer.NO_OP_NORMALIZER);
try {
return operation.wrapData(responseFieldMapper.map(responseReader));
} catch (final Exception e) {
logger.e(e, "Failed to read cached operation data. Operation: %s", operation);
return null;
}
}
});
}
@Nonnull @Override public <D extends Operation.Data, T, V extends Operation.Variables> Response<T> read(
@Nonnull final Operation<D, T, V> operation, @Nonnull final ResponseFieldMapper<D> responseFieldMapper,
@Nonnull final ResponseNormalizer<Record> responseNormalizer, @Nonnull final CacheHeaders cacheHeaders) {
checkNotNull(operation, "operation == null");
checkNotNull(responseNormalizer, "responseNormalizer == null");
checkNotNull(customTypeAdapters, "customTypeAdapters == null");
return readTransaction(new Transaction<ReadableStore, Response<T>>() {
@Nonnull @Override public Response<T> execute(ReadableStore cache) {
Record rootRecord = cache.read(CacheKeyResolver.rootKeyForOperation(operation).key(), cacheHeaders);
if (rootRecord == null) {
return new Response<>(operation);
}
CacheFieldValueResolver fieldValueResolver = new CacheFieldValueResolver(cache, operation.variables(),
cacheKeyResolver(), cacheHeaders);
RealResponseReader<Record> responseReader = new RealResponseReader<>(operation.variables(), rootRecord,
fieldValueResolver, customTypeAdapters, responseNormalizer);
try {
responseNormalizer.willResolveRootQuery(operation);
T data = operation.wrapData(responseFieldMapper.map(responseReader));
return new Response<T>(operation, data, null, responseNormalizer.dependentKeys());
} catch (final Exception e) {
logger.e(e, "Failed to read cached operation data. Operation: %s", operation);
return new Response<>(operation);
}
}
});
}
@Nullable @Override public <F extends GraphqlFragment> F read(
@Nonnull final ResponseFieldMapper<F> responseFieldMapper, @Nonnull final CacheKey cacheKey,
@Nonnull final Operation.Variables variables) {
checkNotNull(responseFieldMapper, "responseFieldMapper == null");
checkNotNull(cacheKey, "cacheKey == null");
checkNotNull(variables, "variables == null");
return readTransaction(new Transaction<ReadableStore, F>() {
@Nullable @Override public F execute(ReadableStore cache) {
Record rootRecord = cache.read(cacheKey.key(), CacheHeaders.NONE);
if (rootRecord == null) {
return null;
}
CacheFieldValueResolver fieldValueResolver = new CacheFieldValueResolver(cache, variables, cacheKeyResolver(),
CacheHeaders.NONE);
//noinspection unchecked
RealResponseReader<Record> responseReader = new RealResponseReader<>(variables, rootRecord, fieldValueResolver,
customTypeAdapters, ResponseNormalizer.NO_OP_NORMALIZER);
try {
return responseFieldMapper.map(responseReader);
} catch (final Exception e) {
logger.e(e, "Failed to read cached fragment data");
return null;
}
}
});
}
}