package com.apollographql.apollo.internal.cache.http;
import com.apollographql.apollo.internal.util.ApolloLogger;
import java.io.IOException;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import static com.apollographql.apollo.api.internal.Utils.checkNotNull;
import static com.apollographql.apollo.internal.cache.http.HttpCache.CACHE_KEY_HEADER;
import static com.apollographql.apollo.internal.cache.http.Utils.closeQuietly;
import static com.apollographql.apollo.internal.cache.http.Utils.isNetworkFirst;
import static com.apollographql.apollo.internal.cache.http.Utils.isNetworkOnly;
import static com.apollographql.apollo.internal.cache.http.Utils.isPrefetchResponse;
import static com.apollographql.apollo.internal.cache.http.Utils.isStale;
import static com.apollographql.apollo.internal.cache.http.Utils.shouldExpireAfterRead;
import static com.apollographql.apollo.internal.cache.http.Utils.shouldSkipCache;
import static com.apollographql.apollo.internal.cache.http.Utils.shouldSkipNetwork;
import static com.apollographql.apollo.internal.cache.http.Utils.strip;
import static com.apollographql.apollo.internal.cache.http.Utils.unsatisfiableCacheRequest;
import static com.apollographql.apollo.internal.cache.http.Utils.withServedDateHeader;
final class HttpCacheInterceptor implements Interceptor {
private final HttpCache cache;
private final ApolloLogger logger;
HttpCacheInterceptor(HttpCache cache, ApolloLogger logger) {
this.cache = checkNotNull(cache, "cache == null");
this.logger = checkNotNull(logger, "logger == null");
}
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (shouldSkipCache(request)) {
logger.d("Skip http cache for request: %s", request);
return chain.proceed(request);
}
if (shouldSkipNetwork(request)) {
logger.d("Read http cache only for request: %s", request);
return cacheOnlyResponse(request);
}
if (isNetworkOnly(request)) {
logger.d("Skip http cache network only request: %s", request);
return networkOnly(request, chain);
}
if (isNetworkFirst(request)) {
logger.d("Network first for request: %s", request);
return networkFirst(request, chain);
} else {
logger.d("Cache first for request: %s", request);
return cacheFirst(request, chain);
}
}
private Response cacheOnlyResponse(Request request) throws IOException {
Response cacheResponse = cachedResponse(request);
if (cacheResponse == null) {
logCacheMiss(request);
return unsatisfiableCacheRequest(request);
}
logCacheHit(request);
return cacheResponse.newBuilder()
.cacheResponse(strip(cacheResponse))
.build();
}
private Response networkOnly(Request request, Chain chain) throws IOException {
String cacheKey = request.header(CACHE_KEY_HEADER);
Response networkResponse = withServedDateHeader(chain.proceed(request));
if (isPrefetchResponse(request)) {
return prefetch(networkResponse, cacheKey);
} else if (networkResponse.isSuccessful()) {
logger.d("Network success, skip http cache for request: %s, with cache key: %s", request, cacheKey);
return cache.cacheProxy(networkResponse, cacheKey);
} else {
return networkResponse;
}
}
private Response networkFirst(Request request, Chain chain) throws IOException {
String cacheKey = request.header(CACHE_KEY_HEADER);
IOException rethrowException;
Response networkResponse = null;
try {
networkResponse = withServedDateHeader(chain.proceed(request));
if (networkResponse.isSuccessful()) {
logger.d("Network success, skip http cache for request: %s, with cache key: %s", request, cacheKey);
return cache.cacheProxy(networkResponse, cacheKey);
}
rethrowException = null;
} catch (IOException e) {
rethrowException = e;
}
Response cachedResponse = cachedResponse(request);
if (cachedResponse == null) {
logCacheMiss(request);
if (rethrowException != null) {
throw rethrowException;
}
return networkResponse;
}
logCacheHit(request);
return cachedResponse.newBuilder()
.cacheResponse(strip(cachedResponse))
.networkResponse(strip(networkResponse))
.request(request)
.build();
}
private Response cacheFirst(Request request, Chain chain) throws IOException {
Response cachedResponse = cachedResponse(request);
if (cachedResponse != null) {
logCacheHit(request);
return cachedResponse.newBuilder()
.cacheResponse(strip(cachedResponse))
.request(request)
.build();
}
logCacheMiss(request);
String cacheKey = request.header(CACHE_KEY_HEADER);
Response networkResponse = withServedDateHeader(chain.proceed(request));
if (isPrefetchResponse(request)) {
return prefetch(networkResponse, cacheKey);
} else if (networkResponse.isSuccessful()) {
return cache.cacheProxy(networkResponse, cacheKey);
}
return networkResponse;
}
private Response prefetch(Response networkResponse, String cacheKey) throws IOException {
if (!networkResponse.isSuccessful()) {
return networkResponse;
}
try {
cache.write(networkResponse, cacheKey);
} finally {
networkResponse.close();
}
Response cachedResponse = cache.read(cacheKey);
if (cachedResponse == null) {
throw new IOException("failed to read prefetch cache response");
}
return cachedResponse
.newBuilder()
.networkResponse(strip(networkResponse))
.build();
}
private Response cachedResponse(Request request) {
String cacheKey = request.header(CACHE_KEY_HEADER);
Response cachedResponse = cache.read(cacheKey, shouldExpireAfterRead(request));
if (cachedResponse == null) {
return null;
}
if (isStale(request, cachedResponse)) {
closeQuietly(cachedResponse);
return null;
}
return cachedResponse;
}
private void logCacheHit(Request request) {
String cacheKey = request.header(CACHE_KEY_HEADER);
logger.d("Cache HIT for request: %s, with cache key: %s", request, cacheKey);
}
private void logCacheMiss(Request request) {
String cacheKey = request.header(CACHE_KEY_HEADER);
logger.d("Cache MISS for request: %s, with cache key: %s", request, cacheKey);
}
}