blob: c0b683a013b5e952623f0136422fa36788e1dd1a [file] [log] [blame]
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.impl.cache;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.hc.client5.http.cache.HttpCacheCASOperation;
import org.apache.hc.client5.http.cache.HttpCacheEntry;
import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
import org.apache.hc.client5.http.cache.HttpCacheStorage;
import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
import org.apache.hc.client5.http.cache.ResourceIOException;
import org.apache.hc.core5.util.Args;
/**
* Abstract cache backend for serialized objects capable of CAS (compare-and-swap) updates.
*
* @since 5.0
*/
public abstract class AbstractSerializingCacheStorage<T, CAS> implements HttpCacheStorage {
private final int maxUpdateRetries;
private final HttpCacheEntrySerializer<T> serializer;
public AbstractSerializingCacheStorage(final int maxUpdateRetries, final HttpCacheEntrySerializer<T> serializer) {
this.maxUpdateRetries = Args.notNegative(maxUpdateRetries, "Max retries");
this.serializer = Args.notNull(serializer, "Cache entry serializer");
}
protected abstract String digestToStorageKey(String key);
protected abstract void store(String storageKey, T storageObject) throws ResourceIOException;
protected abstract T restore(String storageKey) throws ResourceIOException;
protected abstract CAS getForUpdateCAS(String storageKey) throws ResourceIOException;
protected abstract T getStorageObject(CAS cas) throws ResourceIOException;
protected abstract boolean updateCAS(String storageKey, CAS cas, T storageObject) throws ResourceIOException;
protected abstract void delete(String storageKey) throws ResourceIOException;
protected abstract Map<String, T> bulkRestore(Collection<String> storageKeys) throws ResourceIOException;
@Override
public final void putEntry(final String key, final HttpCacheEntry entry) throws ResourceIOException {
final String storageKey = digestToStorageKey(key);
final T storageObject = serializer.serialize(new HttpCacheStorageEntry(key, entry));
store(storageKey, storageObject);
}
@Override
public final HttpCacheEntry getEntry(final String key) throws ResourceIOException {
final String storageKey = digestToStorageKey(key);
final T storageObject = restore(storageKey);
if (storageObject == null) {
return null;
}
final HttpCacheStorageEntry entry = serializer.deserialize(storageObject);
if (key.equals(entry.getKey())) {
return entry.getContent();
} else {
return null;
}
}
@Override
public final void removeEntry(final String key) throws ResourceIOException {
final String storageKey = digestToStorageKey(key);
delete(storageKey);
}
@Override
public final void updateEntry(
final String key,
final HttpCacheCASOperation casOperation) throws HttpCacheUpdateException, ResourceIOException {
int numRetries = 0;
final String storageKey = digestToStorageKey(key);
for (;;) {
final CAS cas = getForUpdateCAS(storageKey);
HttpCacheStorageEntry storageEntry = cas != null ? serializer.deserialize(getStorageObject(cas)) : null;
if (storageEntry != null && !key.equals(storageEntry.getKey())) {
storageEntry = null;
}
final HttpCacheEntry existingEntry = storageEntry != null ? storageEntry.getContent() : null;
final HttpCacheEntry updatedEntry = casOperation.execute(existingEntry);
if (existingEntry == null) {
putEntry(key, updatedEntry);
return;
}
final T storageObject = serializer.serialize(new HttpCacheStorageEntry(key, updatedEntry));
if (!updateCAS(storageKey, cas, storageObject)) {
numRetries++;
if (numRetries >= maxUpdateRetries) {
throw new HttpCacheUpdateException("Cache update failed after " + numRetries + " retries");
}
} else {
return;
}
}
}
@Override
public final Map<String, HttpCacheEntry> getEntries(final Collection<String> keys) throws ResourceIOException {
Args.notNull(keys, "Storage keys");
final List<String> storageKeys = new ArrayList<>(keys.size());
for (final String key: keys) {
storageKeys.add(digestToStorageKey(key));
}
final Map<String, T> storageObjectMap = bulkRestore(storageKeys);
final Map<String, HttpCacheEntry> resultMap = new HashMap<>();
for (final String key: keys) {
final String storageKey = digestToStorageKey(key);
final T storageObject = storageObjectMap.get(storageKey);
if (storageObject != null) {
final HttpCacheStorageEntry entry = serializer.deserialize(storageObject);
if (key.equals(entry.getKey())) {
resultMap.put(key, entry.getContent());
}
}
}
return resultMap;
}
}