Factored out logic shared by Memcached and Ehcache implementations into an abstract base class
This commit is contained in:
parent
f70c974241
commit
f215fdcd32
|
@ -26,28 +26,28 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.cache;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Used by some {@link HttpCacheStorage} implementations to serialize
|
||||
* {@link HttpCacheEntry} instances to a byte representation before
|
||||
* storage.
|
||||
* Serializer / deserializer for {@link HttpCacheStorageEntry} entries.
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public interface HttpCacheEntrySerializer {
|
||||
public interface HttpCacheEntrySerializer<T> {
|
||||
|
||||
/**
|
||||
* Serializes the given entry to a byte representation on the
|
||||
* given {@link OutputStream}.
|
||||
* Serializes the given entry.
|
||||
*
|
||||
* @param entry cache entry
|
||||
* @return serialized representation of the cache entry
|
||||
* @throws ResourceIOException
|
||||
*/
|
||||
void writeTo(HttpCacheEntry entry, OutputStream os) throws ResourceIOException;
|
||||
T serialize(HttpCacheStorageEntry entry) throws ResourceIOException;
|
||||
|
||||
/**
|
||||
* Deserializes a byte representation of a cache entry by reading
|
||||
* from the given {@link InputStream}.
|
||||
* Deserializes a cache entry from its serialized representation.
|
||||
* @param serializedObject serialized representation of the cache entry
|
||||
* @return cache entry
|
||||
* @throws ResourceIOException
|
||||
*/
|
||||
HttpCacheEntry readFrom(InputStream is) throws ResourceIOException;
|
||||
HttpCacheStorageEntry deserialize(T serializedObject) throws ResourceIOException;
|
||||
|
||||
}
|
||||
|
|
|
@ -24,22 +24,33 @@
|
|||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache.memcached;
|
||||
package org.apache.hc.client5.http.cache;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link MemcachedCacheEntryFactory}.
|
||||
*/
|
||||
public class MemcachedCacheEntryFactoryImpl implements MemcachedCacheEntryFactory {
|
||||
import org.apache.hc.core5.util.Args;
|
||||
|
||||
@Override
|
||||
public MemcachedCacheEntry getMemcachedCacheEntry(final String key, final HttpCacheEntry entry) {
|
||||
return new MemcachedCacheEntryImpl(key, entry);
|
||||
public final class HttpCacheStorageEntry implements Serializable {
|
||||
|
||||
private final String key;
|
||||
private final HttpCacheEntry content;
|
||||
|
||||
public HttpCacheStorageEntry(final String key, final HttpCacheEntry content) {
|
||||
this.key = key;
|
||||
this.content = Args.notNull(content, "Cache entry");
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public HttpCacheEntry getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemcachedCacheEntry getUnsetCacheEntry() {
|
||||
return new MemcachedCacheEntryImpl(null, null);
|
||||
public String toString() {
|
||||
return "[key=" + key + "; content=" + content + "]";
|
||||
}
|
||||
|
||||
}
|
|
@ -27,8 +27,8 @@
|
|||
package org.apache.hc.client5.http.cache;
|
||||
|
||||
/**
|
||||
* Signals that {@link HttpCacheStorage} encountered an error performing an
|
||||
* processChallenge operation.
|
||||
* Signals that {@link HttpCacheStorage} encountered an error performing
|
||||
* an update operation.
|
||||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
|
|
|
@ -24,18 +24,23 @@
|
|||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
package org.apache.hc.client5.http.impl.cache.memcached;
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
|
||||
|
||||
/**
|
||||
* Raised when there is a problem serializing or deserializing cache
|
||||
* entries into a byte representation suitable for memcached storage.
|
||||
* Abstract cache backend for serialized binary objects capable of CAS (compare-and-swap) updates.
|
||||
*
|
||||
* @since 5.0
|
||||
*/
|
||||
public class MemcachedSerializationException extends ResourceIOException {
|
||||
public abstract class AbstractBinaryCacheStorage<CAS> extends AbstractSerializingCacheStorage<byte[], CAS> {
|
||||
|
||||
public MemcachedSerializationException(final Throwable cause) {
|
||||
super(cause != null ? cause.getMessage() : null, cause);
|
||||
public AbstractBinaryCacheStorage(final int maxUpdateRetries, final HttpCacheEntrySerializer<byte[]> serializer) {
|
||||
super(maxUpdateRetries, serializer);
|
||||
}
|
||||
|
||||
public AbstractBinaryCacheStorage(final int maxUpdateRetries) {
|
||||
super(maxUpdateRetries, ByteArrayCacheEntrySerializer.INSTANCE);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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 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.HttpCacheUpdateCallback;
|
||||
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;
|
||||
|
||||
@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 HttpCacheUpdateCallback callback) 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 = callback.update(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -26,14 +26,14 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.impl.cache;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.core5.annotation.Contract;
|
||||
import org.apache.hc.core5.annotation.ThreadingBehavior;
|
||||
|
@ -47,21 +47,31 @@ import org.apache.hc.core5.annotation.ThreadingBehavior;
|
|||
* @since 4.1
|
||||
*/
|
||||
@Contract(threading = ThreadingBehavior.IMMUTABLE)
|
||||
public class DefaultHttpCacheEntrySerializer implements HttpCacheEntrySerializer {
|
||||
public final class ByteArrayCacheEntrySerializer implements HttpCacheEntrySerializer<byte[]> {
|
||||
|
||||
public static final ByteArrayCacheEntrySerializer INSTANCE = new ByteArrayCacheEntrySerializer();
|
||||
|
||||
@Override
|
||||
public void writeTo(final HttpCacheEntry cacheEntry, final OutputStream os) throws ResourceIOException {
|
||||
try (final ObjectOutputStream oos = new ObjectOutputStream(os)) {
|
||||
public byte[] serialize(final HttpCacheStorageEntry cacheEntry) throws ResourceIOException {
|
||||
if (cacheEntry == null) {
|
||||
return null;
|
||||
}
|
||||
final ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
try (final ObjectOutputStream oos = new ObjectOutputStream(buf)) {
|
||||
oos.writeObject(cacheEntry);
|
||||
} catch (final IOException ex) {
|
||||
throw new ResourceIOException(ex.getMessage(), ex);
|
||||
}
|
||||
return buf.toByteArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCacheEntry readFrom(final InputStream is) throws ResourceIOException {
|
||||
try (final ObjectInputStream ois = new ObjectInputStream(is)) {
|
||||
return (HttpCacheEntry) ois.readObject();
|
||||
public HttpCacheStorageEntry deserialize(final byte[] serializedObject) throws ResourceIOException {
|
||||
if (serializedObject == null) {
|
||||
return null;
|
||||
}
|
||||
try (final ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(serializedObject))) {
|
||||
return (HttpCacheStorageEntry) ois.readObject();
|
||||
} catch (final IOException | ClassNotFoundException ex) {
|
||||
throw new ResourceIOException(ex.getMessage(), ex);
|
||||
}
|
|
@ -26,17 +26,12 @@
|
|||
*/
|
||||
package org.apache.hc.client5.http.impl.cache.ehcache;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
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.HttpCacheUpdateCallback;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.client5.http.impl.cache.AbstractBinaryCacheStorage;
|
||||
import org.apache.hc.client5.http.impl.cache.CacheConfig;
|
||||
import org.apache.hc.client5.http.impl.cache.DefaultHttpCacheEntrySerializer;
|
||||
import org.apache.hc.client5.http.impl.cache.ByteArrayCacheEntrySerializer;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
|
||||
import net.sf.ehcache.Ehcache;
|
||||
import net.sf.ehcache.Element;
|
||||
|
@ -58,31 +53,17 @@ import net.sf.ehcache.Element;
|
|||
* itself.</p>
|
||||
* @since 4.1
|
||||
*/
|
||||
public class EhcacheHttpCacheStorage implements HttpCacheStorage {
|
||||
public class EhcacheHttpCacheStorage extends AbstractBinaryCacheStorage<Element> {
|
||||
|
||||
private final Ehcache cache;
|
||||
private final HttpCacheEntrySerializer serializer;
|
||||
private final int maxUpdateRetries;
|
||||
|
||||
/**
|
||||
* Constructs a storage backend using the provided Ehcache
|
||||
* with default configuration options.
|
||||
* @param cache where to store cached origin responses
|
||||
*/
|
||||
public EhcacheHttpCacheStorage(final Ehcache cache) {
|
||||
this(cache, CacheConfig.DEFAULT, new DefaultHttpCacheEntrySerializer());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a storage backend using the provided Ehcache
|
||||
* with the given configuration options.
|
||||
* @param cache where to store cached origin responses
|
||||
* @param config cache storage configuration options - note that
|
||||
* the setting for max object size <b>will be ignored</b> and
|
||||
* should be configured in the Ehcache instead.
|
||||
*/
|
||||
public EhcacheHttpCacheStorage(final Ehcache cache, final CacheConfig config){
|
||||
this(cache, config, new DefaultHttpCacheEntrySerializer());
|
||||
public EhcacheHttpCacheStorage(final Ehcache cache){
|
||||
this(cache, CacheConfig.DEFAULT, ByteArrayCacheEntrySerializer.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -95,67 +76,60 @@ public class EhcacheHttpCacheStorage implements HttpCacheStorage {
|
|||
* should be configured in the Ehcache instead.
|
||||
* @param serializer alternative serialization mechanism
|
||||
*/
|
||||
public EhcacheHttpCacheStorage(final Ehcache cache, final CacheConfig config, final HttpCacheEntrySerializer serializer){
|
||||
this.cache = cache;
|
||||
this.maxUpdateRetries = config.getMaxUpdateRetries();
|
||||
this.serializer = serializer;
|
||||
public EhcacheHttpCacheStorage(
|
||||
final Ehcache cache,
|
||||
final CacheConfig config,
|
||||
final HttpCacheEntrySerializer<byte[]> serializer) {
|
||||
super((config != null ? config : CacheConfig.DEFAULT).getMaxUpdateRetries(), serializer);
|
||||
this.cache = Args.notNull(cache, "Ehcache");
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void putEntry(final String key, final HttpCacheEntry entry) throws ResourceIOException {
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
serializer.writeTo(entry, bos);
|
||||
cache.put(new Element(key, bos.toByteArray()));
|
||||
protected String digestToStorageKey(final String key) {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized HttpCacheEntry getEntry(final String key) throws ResourceIOException {
|
||||
final Element e = cache.get(key);
|
||||
if(e == null){
|
||||
protected void store(final String storageKey, final byte[] storageObject) throws ResourceIOException {
|
||||
cache.put(new Element(storageKey, storageKey));
|
||||
}
|
||||
|
||||
private byte[] castAsByteArray(final Object storageObject) throws ResourceIOException {
|
||||
if (storageObject == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final byte[] data = (byte[])e.getObjectValue();
|
||||
return serializer.readFrom(new ByteArrayInputStream(data));
|
||||
if (storageObject instanceof byte[]) {
|
||||
return (byte[]) storageObject;
|
||||
} else {
|
||||
throw new ResourceIOException("Unexpected cache content: " + storageObject.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void removeEntry(final String key) {
|
||||
cache.remove(key);
|
||||
protected byte[] restore(final String storageKey) throws ResourceIOException {
|
||||
final Element element = cache.get(storageKey);
|
||||
return element != null ? castAsByteArray(element.getObjectValue()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void updateEntry(
|
||||
final String key, final HttpCacheUpdateCallback callback) throws ResourceIOException, HttpCacheUpdateException {
|
||||
int numRetries = 0;
|
||||
do{
|
||||
final Element oldElement = cache.get(key);
|
||||
|
||||
HttpCacheEntry existingEntry = null;
|
||||
if(oldElement != null){
|
||||
final byte[] data = (byte[])oldElement.getObjectValue();
|
||||
existingEntry = serializer.readFrom(new ByteArrayInputStream(data));
|
||||
}
|
||||
|
||||
final HttpCacheEntry updatedEntry = callback.update(existingEntry);
|
||||
|
||||
if (existingEntry == null) {
|
||||
putEntry(key, updatedEntry);
|
||||
return;
|
||||
} else {
|
||||
// Attempt to do a CAS replace, if we fail then retry
|
||||
// While this operation should work fine within this instance, multiple instances
|
||||
// could trample each others' data
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
serializer.writeTo(updatedEntry, bos);
|
||||
final Element newElement = new Element(key, bos.toByteArray());
|
||||
if (cache.replace(oldElement, newElement)) {
|
||||
return;
|
||||
}else{
|
||||
numRetries++;
|
||||
}
|
||||
}
|
||||
}while(numRetries <= maxUpdateRetries);
|
||||
throw new HttpCacheUpdateException("Failed to processChallenge");
|
||||
protected Element getForUpdateCAS(final String storageKey) throws ResourceIOException {
|
||||
return cache.get(storageKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] getStorageObject(final Element element) throws ResourceIOException {
|
||||
return castAsByteArray(element.getObjectValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean updateCAS(final String storageKey, final Element element, final byte[] storageObject) throws ResourceIOException {
|
||||
final Element newElement = new Element(storageKey, storageObject);
|
||||
return cache.replace(element, newElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void delete(final String storageKey) throws ResourceIOException {
|
||||
cache.remove(storageKey);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.memcached;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
|
||||
/**
|
||||
* Provides for serialization and deserialization of higher-level
|
||||
* {@link HttpCacheEntry} objects into byte arrays suitable for
|
||||
* storage in memcached. Clients wishing to change the serialization
|
||||
* mechanism from the provided defaults should implement this
|
||||
* interface as well as {@link MemcachedCacheEntryFactory}.
|
||||
*/
|
||||
public interface MemcachedCacheEntry {
|
||||
|
||||
/**
|
||||
* Returns a serialized representation of the current cache entry.
|
||||
* @throws MemcachedSerializationException if serialization fails.
|
||||
* */
|
||||
byte[] toByteArray() throws MemcachedSerializationException;
|
||||
|
||||
/**
|
||||
* Returns the storage key associated with this entry. May return
|
||||
* {@code null} if this is an "unset" instance waiting to be
|
||||
* {@link #set(byte[])} with a serialized representation.
|
||||
*/
|
||||
String getStorageKey();
|
||||
|
||||
/**
|
||||
* Returns the {@link HttpCacheEntry} associated with this entry.
|
||||
* May return {@code null} if this is an "unset" instance
|
||||
* waiting to be {@link #set(byte[])} with a serialized
|
||||
* representation.
|
||||
*/
|
||||
HttpCacheEntry getHttpCacheEntry();
|
||||
|
||||
/**
|
||||
* Given a serialized representation of a {@link MemcachedCacheEntry},
|
||||
* attempt to reconstitute the storage key and {@link HttpCacheEntry}
|
||||
* represented therein. After a successful call to this method, this
|
||||
* object should return updated (as appropriate) values for
|
||||
* {@link #getStorageKey()} and {@link #getHttpCacheEntry()}. This
|
||||
* should be viewed as an atomic operation on the
|
||||
* {@code MemcachedCacheEntry}.
|
||||
*
|
||||
* @param bytes serialized representation
|
||||
* @throws MemcachedSerializationException if deserialization
|
||||
* fails. In this case, the prior values for {{@link #getStorageKey()}
|
||||
* and {@link #getHttpCacheEntry()} should remain unchanged.
|
||||
*/
|
||||
void set(byte[] bytes) throws MemcachedSerializationException ;
|
||||
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.memcached;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
|
||||
/**
|
||||
* Creates {@link MemcachedCacheEntry} instances that can be used for
|
||||
* serializing and deserializing {@link HttpCacheEntry} instances for
|
||||
* storage in memcached.
|
||||
*/
|
||||
public interface MemcachedCacheEntryFactory {
|
||||
|
||||
/**
|
||||
* Creates a new {@link MemcachedCacheEntry} for storing the
|
||||
* given {@link HttpCacheEntry} under the given storage key. Since
|
||||
* we are hashing storage keys into cache keys to accommodate
|
||||
* limitations in memcached's key space, it is possible to have
|
||||
* cache collisions. Therefore, we store the storage key along
|
||||
* with the {@code HttpCacheEntry} so it can be compared
|
||||
* on retrieval and thus detect collisions.
|
||||
* @param storageKey storage key under which the entry will
|
||||
* be logically stored
|
||||
* @param entry the cache entry to store
|
||||
* @return a {@link MemcachedCacheEntry} ready to provide
|
||||
* a serialized representation
|
||||
*/
|
||||
MemcachedCacheEntry getMemcachedCacheEntry(String storageKey, HttpCacheEntry entry);
|
||||
|
||||
/**
|
||||
* Creates an "unset" {@link MemcachedCacheEntry} ready to accept
|
||||
* a serialized representation via {@link MemcachedCacheEntry#set(byte[])}
|
||||
* and deserialize it into a storage key and a {@link HttpCacheEntry}.
|
||||
* @return {@code MemcachedCacheEntry}
|
||||
*/
|
||||
MemcachedCacheEntry getUnsetCacheEntry();
|
||||
|
||||
}
|
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.memcached;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link MemcachedCacheEntry}. This implementation
|
||||
* simply uses Java serialization to serialize the storage key followed by
|
||||
* the {@link HttpCacheEntry} into a byte array.
|
||||
*/
|
||||
public class MemcachedCacheEntryImpl implements MemcachedCacheEntry {
|
||||
|
||||
private String key;
|
||||
private HttpCacheEntry httpCacheEntry;
|
||||
|
||||
public MemcachedCacheEntryImpl(final String key, final HttpCacheEntry httpCacheEntry) {
|
||||
this.key = key;
|
||||
this.httpCacheEntry = httpCacheEntry;
|
||||
}
|
||||
|
||||
public MemcachedCacheEntryImpl() {
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.http.impl.client.cache.memcached.MemcachedCacheEntry#toByteArray()
|
||||
*/
|
||||
@Override
|
||||
synchronized public byte[] toByteArray() throws MemcachedSerializationException {
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
final ObjectOutputStream oos;
|
||||
try {
|
||||
oos = new ObjectOutputStream(bos);
|
||||
oos.writeObject(this.key);
|
||||
oos.writeObject(this.httpCacheEntry);
|
||||
oos.close();
|
||||
} catch (final IOException ioe) {
|
||||
throw new MemcachedSerializationException(ioe);
|
||||
}
|
||||
return bos.toByteArray();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.http.impl.client.cache.memcached.MemcachedCacheEntry#getKey()
|
||||
*/
|
||||
@Override
|
||||
public synchronized String getStorageKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.http.impl.client.cache.memcached.MemcachedCacheEntry#getHttpCacheEntry()
|
||||
*/
|
||||
@Override
|
||||
public synchronized HttpCacheEntry getHttpCacheEntry() {
|
||||
return httpCacheEntry;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see org.apache.http.impl.client.cache.memcached.MemcachedCacheEntry#set(byte[])
|
||||
*/
|
||||
@Override
|
||||
synchronized public void set(final byte[] bytes) throws MemcachedSerializationException {
|
||||
final ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
|
||||
final ObjectInputStream ois;
|
||||
final String s;
|
||||
final HttpCacheEntry entry;
|
||||
try {
|
||||
ois = new ObjectInputStream(bis);
|
||||
s = (String)ois.readObject();
|
||||
entry = (HttpCacheEntry)ois.readObject();
|
||||
ois.close();
|
||||
bis.close();
|
||||
} catch (final IOException | ClassNotFoundException ioe) {
|
||||
throw new MemcachedSerializationException(ioe);
|
||||
}
|
||||
this.key = s;
|
||||
this.httpCacheEntry = entry;
|
||||
}
|
||||
|
||||
}
|
|
@ -29,14 +29,12 @@ package org.apache.hc.client5.http.impl.cache.memcached;
|
|||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheStorage;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheUpdateCallback;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.client5.http.impl.cache.AbstractBinaryCacheStorage;
|
||||
import org.apache.hc.client5.http.impl.cache.CacheConfig;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.hc.client5.http.impl.cache.ByteArrayCacheEntrySerializer;
|
||||
import org.apache.hc.core5.util.Args;
|
||||
|
||||
import net.spy.memcached.CASResponse;
|
||||
import net.spy.memcached.CASValue;
|
||||
|
@ -73,15 +71,6 @@ import net.spy.memcached.OperationTimeoutException;
|
|||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Because this hashing scheme can potentially result in key collisions (though
|
||||
* highly unlikely), we need to store the higher-level logical storage key along
|
||||
* with the {@link HttpCacheEntry} so that we can re-check it on retrieval. There
|
||||
* is a default serialization scheme provided for this, although you can provide
|
||||
* your own implementations of {@link MemcachedCacheEntry} and
|
||||
* {@link MemcachedCacheEntryFactory} to customize this serialization.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Please refer to the <a href="http://code.google.com/p/memcached/wiki/NewStart">
|
||||
* memcached documentation</a> and in particular to the documentation for
|
||||
* the <a href="http://code.google.com/p/spymemcached/">spymemcached
|
||||
|
@ -91,14 +80,10 @@ import net.spy.memcached.OperationTimeoutException;
|
|||
*
|
||||
* @since 4.1
|
||||
*/
|
||||
public class MemcachedHttpCacheStorage implements HttpCacheStorage {
|
||||
|
||||
private final Logger log = LogManager.getLogger(getClass());
|
||||
public class MemcachedHttpCacheStorage extends AbstractBinaryCacheStorage<CASValue<Object>> {
|
||||
|
||||
private final MemcachedClientIF client;
|
||||
private final KeyHashingScheme keyHashingScheme;
|
||||
private final MemcachedCacheEntryFactory memcachedCacheEntryFactory;
|
||||
private final int maxUpdateRetries;
|
||||
|
||||
/**
|
||||
* Create a storage backend talking to a <i>memcached</i> instance
|
||||
|
@ -118,8 +103,7 @@ public class MemcachedHttpCacheStorage implements HttpCacheStorage {
|
|||
* @param cache client to use for communicating with <i>memcached</i>
|
||||
*/
|
||||
public MemcachedHttpCacheStorage(final MemcachedClientIF cache) {
|
||||
this(cache, CacheConfig.DEFAULT, new MemcachedCacheEntryFactoryImpl(),
|
||||
new SHA256KeyHashingScheme());
|
||||
this(cache, CacheConfig.DEFAULT, ByteArrayCacheEntrySerializer.INSTANCE, SHA256KeyHashingScheme.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -128,140 +112,83 @@ public class MemcachedHttpCacheStorage implements HttpCacheStorage {
|
|||
* mechanisms.
|
||||
* @param client how to talk to <i>memcached</i>
|
||||
* @param config apply HTTP cache-related options
|
||||
* @param memcachedCacheEntryFactory Factory pattern used for obtaining
|
||||
* instances of alternative cache entry serialization mechanisms
|
||||
* @param serializer alternative serialization mechanism
|
||||
* @param keyHashingScheme how to map higher-level logical "storage keys"
|
||||
* onto "cache keys" suitable for use with memcached
|
||||
*/
|
||||
public MemcachedHttpCacheStorage(final MemcachedClientIF client, final CacheConfig config,
|
||||
final MemcachedCacheEntryFactory memcachedCacheEntryFactory,
|
||||
public MemcachedHttpCacheStorage(
|
||||
final MemcachedClientIF client,
|
||||
final CacheConfig config,
|
||||
final HttpCacheEntrySerializer<byte[]> serializer,
|
||||
final KeyHashingScheme keyHashingScheme) {
|
||||
this.client = client;
|
||||
this.maxUpdateRetries = config.getMaxUpdateRetries();
|
||||
this.memcachedCacheEntryFactory = memcachedCacheEntryFactory;
|
||||
super((config != null ? config : CacheConfig.DEFAULT).getMaxUpdateRetries(),
|
||||
serializer != null ? serializer : ByteArrayCacheEntrySerializer.INSTANCE);
|
||||
this.client = Args.notNull(client, "Memcached client");
|
||||
this.keyHashingScheme = keyHashingScheme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putEntry(final String url, final HttpCacheEntry entry) throws ResourceIOException {
|
||||
final byte[] bytes = serializeEntry(url, entry);
|
||||
final String key = getCacheKey(url);
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
protected String digestToStorageKey(final String key) {
|
||||
return keyHashingScheme.hash(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void store(final String storageKey, final byte[] storageObject) throws ResourceIOException {
|
||||
try {
|
||||
client.set(key, 0, bytes);
|
||||
client.set(storageKey, 0, storageObject);
|
||||
} catch (final OperationTimeoutException ex) {
|
||||
throw new MemcachedOperationTimeoutException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private String getCacheKey(final String url) {
|
||||
try {
|
||||
return keyHashingScheme.hash(url);
|
||||
} catch (final MemcachedKeyHashingException mkhe) {
|
||||
private byte[] castAsByteArray(final Object storageObject) throws ResourceIOException {
|
||||
if (storageObject == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] serializeEntry(final String url, final HttpCacheEntry hce) throws ResourceIOException {
|
||||
final MemcachedCacheEntry mce = memcachedCacheEntryFactory.getMemcachedCacheEntry(url, hce);
|
||||
return mce.toByteArray();
|
||||
}
|
||||
|
||||
private byte[] convertToByteArray(final Object o) {
|
||||
if (o == null) {
|
||||
return null;
|
||||
if (storageObject instanceof byte[]) {
|
||||
return (byte[]) storageObject;
|
||||
} else {
|
||||
throw new ResourceIOException("Unexpected cache content: " + storageObject.getClass());
|
||||
}
|
||||
if (!(o instanceof byte[])) {
|
||||
log.warn("got a non-bytearray back from memcached: " + o);
|
||||
return null;
|
||||
}
|
||||
return (byte[])o;
|
||||
}
|
||||
|
||||
private MemcachedCacheEntry reconstituteEntry(final Object o) {
|
||||
final byte[] bytes = convertToByteArray(o);
|
||||
if (bytes == null) {
|
||||
return null;
|
||||
}
|
||||
final MemcachedCacheEntry mce = memcachedCacheEntryFactory.getUnsetCacheEntry();
|
||||
try {
|
||||
mce.set(bytes);
|
||||
} catch (final MemcachedSerializationException mse) {
|
||||
return null;
|
||||
}
|
||||
return mce;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpCacheEntry getEntry(final String url) throws ResourceIOException {
|
||||
final String key = getCacheKey(url);
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
protected byte[] restore(final String storageKey) throws ResourceIOException {
|
||||
try {
|
||||
final MemcachedCacheEntry mce = reconstituteEntry(client.get(key));
|
||||
if (mce == null || !url.equals(mce.getStorageKey())) {
|
||||
return null;
|
||||
}
|
||||
return mce.getHttpCacheEntry();
|
||||
return castAsByteArray(client.get(storageKey));
|
||||
} catch (final OperationTimeoutException ex) {
|
||||
throw new MemcachedOperationTimeoutException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(final String url) throws ResourceIOException {
|
||||
final String key = getCacheKey(url);
|
||||
if (key == null) {
|
||||
return;
|
||||
}
|
||||
protected CASValue<Object> getForUpdateCAS(final String storageKey) throws ResourceIOException {
|
||||
try {
|
||||
client.delete(key);
|
||||
return client.gets(storageKey);
|
||||
} catch (final OperationTimeoutException ex) {
|
||||
throw new MemcachedOperationTimeoutException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateEntry(final String url, final HttpCacheUpdateCallback callback)
|
||||
throws HttpCacheUpdateException, ResourceIOException {
|
||||
int numRetries = 0;
|
||||
final String key = getCacheKey(url);
|
||||
if (key == null) {
|
||||
throw new HttpCacheUpdateException("couldn't generate cache key");
|
||||
}
|
||||
do {
|
||||
try {
|
||||
final CASValue<Object> v = client.gets(key);
|
||||
MemcachedCacheEntry mce = (v == null) ? null
|
||||
: reconstituteEntry(v.getValue());
|
||||
if (mce != null && (!url.equals(mce.getStorageKey()))) {
|
||||
mce = null;
|
||||
}
|
||||
final HttpCacheEntry existingEntry = (mce == null) ? null
|
||||
: mce.getHttpCacheEntry();
|
||||
final HttpCacheEntry updatedEntry = callback.update(existingEntry);
|
||||
|
||||
if (existingEntry == null) {
|
||||
putEntry(url, updatedEntry);
|
||||
return;
|
||||
|
||||
}
|
||||
final byte[] updatedBytes = serializeEntry(url, updatedEntry);
|
||||
final CASResponse casResult = client.cas(key, v.getCas(),
|
||||
updatedBytes);
|
||||
if (casResult != CASResponse.OK) {
|
||||
numRetries++;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} catch (final OperationTimeoutException ex) {
|
||||
throw new MemcachedOperationTimeoutException(ex);
|
||||
}
|
||||
} while (numRetries <= maxUpdateRetries);
|
||||
|
||||
throw new HttpCacheUpdateException("Failed to processChallenge");
|
||||
protected byte[] getStorageObject(final CASValue<Object> casValue) throws ResourceIOException {
|
||||
return castAsByteArray(casValue.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean updateCAS(
|
||||
final String storageKey, final CASValue<Object> casValue, final byte[] storageObject) throws ResourceIOException {
|
||||
final CASResponse casResult = client.cas(storageKey, casValue.getCas(), storageObject);
|
||||
return casResult == CASResponse.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void delete(final String storageKey) throws ResourceIOException {
|
||||
try {
|
||||
client.delete(storageKey);
|
||||
} catch (final OperationTimeoutException ex) {
|
||||
throw new MemcachedOperationTimeoutException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ package org.apache.hc.client5.http.impl.cache.memcached;
|
|||
* Primarily useful for namespacing a shared memcached cluster, for
|
||||
* example.
|
||||
*/
|
||||
public class PrefixKeyHashingScheme implements KeyHashingScheme {
|
||||
public final class PrefixKeyHashingScheme implements KeyHashingScheme {
|
||||
|
||||
private final String prefix;
|
||||
private final KeyHashingScheme backingScheme;
|
||||
|
|
|
@ -40,7 +40,9 @@ import org.apache.logging.log4j.Logger;
|
|||
* digests and hence are always 64-character hexadecimal
|
||||
* strings.
|
||||
*/
|
||||
public class SHA256KeyHashingScheme implements KeyHashingScheme {
|
||||
public final class SHA256KeyHashingScheme implements KeyHashingScheme {
|
||||
|
||||
public static final SHA256KeyHashingScheme INSTANCE = new SHA256KeyHashingScheme();
|
||||
|
||||
private final Logger log = LogManager.getLogger(getClass());
|
||||
|
||||
|
|
100
httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpCacheEntryMatcher.java
vendored
Normal file
100
httpclient5-cache/src/test/java/org/apache/hc/client5/http/impl/cache/HttpCacheEntryMatcher.java
vendored
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.Resource;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.hamcrest.BaseMatcher;
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.Factory;
|
||||
import org.hamcrest.Matcher;
|
||||
|
||||
public class HttpCacheEntryMatcher extends BaseMatcher<HttpCacheEntry> {
|
||||
|
||||
private final HttpCacheEntry expectedValue;
|
||||
|
||||
public HttpCacheEntryMatcher(final HttpCacheEntry expectedValue) {
|
||||
this.expectedValue = expectedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(final Object item) {
|
||||
if (item instanceof HttpCacheEntry) {
|
||||
try {
|
||||
final HttpCacheEntry otherValue = (HttpCacheEntry) item;
|
||||
|
||||
final int expectedStatus = expectedValue.getStatus();
|
||||
final int otherStatus = otherValue.getStatus();
|
||||
if (expectedStatus != otherStatus) {
|
||||
return false;
|
||||
}
|
||||
final Date expectedRequestDate = expectedValue.getRequestDate();
|
||||
final Date otherRequestDate = otherValue.getRequestDate();
|
||||
if (!Objects.equals(expectedRequestDate, otherRequestDate)) {
|
||||
return false;
|
||||
}
|
||||
final Date expectedResponseDate = expectedValue.getResponseDate();
|
||||
final Date otherResponseDate = otherValue.getResponseDate();
|
||||
if (!Objects.equals(expectedResponseDate, otherResponseDate)) {
|
||||
return false;
|
||||
}
|
||||
final Header[] expectedHeaders = expectedValue.getAllHeaders();
|
||||
final Header[] otherHeaders = otherValue.getAllHeaders();
|
||||
if (!Arrays.deepEquals(expectedHeaders, otherHeaders)) {
|
||||
return false;
|
||||
}
|
||||
final Resource expectedResource = expectedValue.getResource();
|
||||
final byte[] expectedContent = expectedResource != null ? expectedResource.get() : null;
|
||||
final Resource otherResource = otherValue.getResource();
|
||||
final byte[] otherContent = otherResource != null ? otherResource.get() : null;
|
||||
if (!Arrays.equals(expectedContent, otherContent)) {
|
||||
return false;
|
||||
}
|
||||
} catch (final ResourceIOException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(final Description description) {
|
||||
description.appendValue(expectedValue);
|
||||
}
|
||||
|
||||
@Factory
|
||||
public static Matcher<HttpCacheEntry> equivalent(final HttpCacheEntry target) {
|
||||
return new HttpCacheEntryMatcher(target);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,256 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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 static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheUpdateCallback;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
|
||||
import org.apache.hc.client5.http.cache.ResourceIOException;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Answers;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
@SuppressWarnings("boxing") // test code
|
||||
public class TestAbstractSerializingCacheStorage {
|
||||
|
||||
public static byte[] serialize(final String key, final HttpCacheEntry value) throws ResourceIOException {
|
||||
return ByteArrayCacheEntrySerializer.INSTANCE.serialize(new HttpCacheStorageEntry(key, value));
|
||||
}
|
||||
|
||||
private AbstractBinaryCacheStorage<String> impl;
|
||||
|
||||
@Before
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setUp() {
|
||||
impl = Mockito.mock(AbstractBinaryCacheStorage.class,
|
||||
Mockito.withSettings().defaultAnswer(Answers.CALLS_REAL_METHODS).useConstructor(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCachePut() throws Exception {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
when(impl.digestToStorageKey(key)).thenReturn("bar");
|
||||
|
||||
impl.putEntry(key, value);
|
||||
|
||||
final ArgumentCaptor<byte[]> argumentCaptor = ArgumentCaptor.forClass(byte[].class);
|
||||
verify(impl).store(eq("bar"), argumentCaptor.capture());
|
||||
Assert.assertArrayEquals(serialize(key, value), argumentCaptor.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheGetNullEntry() throws Exception {
|
||||
final String key = "foo";
|
||||
|
||||
when(impl.digestToStorageKey(key)).thenReturn("bar");
|
||||
when(impl.restore("bar")).thenReturn(null);
|
||||
|
||||
final HttpCacheEntry resultingEntry = impl.getEntry(key);
|
||||
|
||||
verify(impl).restore("bar");
|
||||
|
||||
Assert.assertThat(resultingEntry, CoreMatchers.nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheGet() throws Exception {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
when(impl.digestToStorageKey(key)).thenReturn("bar");
|
||||
when(impl.restore("bar")).thenReturn(serialize(key, value));
|
||||
|
||||
final HttpCacheEntry resultingEntry = impl.getEntry(key);
|
||||
|
||||
verify(impl).restore("bar");
|
||||
|
||||
Assert.assertThat(resultingEntry, HttpCacheEntryMatcher.equivalent(value));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheGetKeyMismatch() throws Exception {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
when(impl.digestToStorageKey(key)).thenReturn("bar");
|
||||
when(impl.restore("bar")).thenReturn(serialize("not-foo", value));
|
||||
|
||||
final HttpCacheEntry resultingEntry = impl.getEntry(key);
|
||||
|
||||
verify(impl).restore("bar");
|
||||
|
||||
Assert.assertThat(resultingEntry, CoreMatchers.nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheRemove() throws Exception{
|
||||
final String key = "foo";
|
||||
|
||||
when(impl.digestToStorageKey(key)).thenReturn("bar");
|
||||
impl.removeEntry(key);
|
||||
|
||||
verify(impl).delete("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateNullEntry() throws Exception {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
when(impl.digestToStorageKey(key)).thenReturn("bar");
|
||||
when(impl.getForUpdateCAS("bar")).thenReturn(null);
|
||||
|
||||
impl.updateEntry(key, new HttpCacheUpdateCallback() {
|
||||
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry existing) throws ResourceIOException {
|
||||
Assert.assertThat(existing, CoreMatchers.nullValue());
|
||||
return updatedValue;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
verify(impl).getForUpdateCAS("bar");
|
||||
verify(impl).store(Mockito.eq("bar"), Mockito.<byte[]>any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheCASUpdate() throws Exception {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
when(impl.digestToStorageKey(key)).thenReturn("bar");
|
||||
when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
|
||||
when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
|
||||
when(impl.updateCAS(Mockito.eq("bar"), Mockito.eq("stuff"), Mockito.<byte[]>any())).thenReturn(true);
|
||||
|
||||
impl.updateEntry(key, new HttpCacheUpdateCallback() {
|
||||
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry existing) throws ResourceIOException {
|
||||
return updatedValue;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
verify(impl).getForUpdateCAS("bar");
|
||||
verify(impl).getStorageObject("stuff");
|
||||
verify(impl).updateCAS(Mockito.eq("bar"), Mockito.eq("stuff"), Mockito.<byte[]>any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheCASUpdateKeyMismatch() throws Exception {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
when(impl.digestToStorageKey(key)).thenReturn("bar");
|
||||
when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
|
||||
when(impl.getStorageObject("stuff")).thenReturn(serialize("not-foo", existingValue));
|
||||
when(impl.updateCAS(Mockito.eq("bar"), Mockito.eq("stuff"), Mockito.<byte[]>any())).thenReturn(true);
|
||||
|
||||
impl.updateEntry(key, new HttpCacheUpdateCallback() {
|
||||
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry existing) throws ResourceIOException {
|
||||
Assert.assertThat(existing, CoreMatchers.nullValue());
|
||||
return updatedValue;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
verify(impl).getForUpdateCAS("bar");
|
||||
verify(impl).getStorageObject("stuff");
|
||||
verify(impl).store(Mockito.eq("bar"), Mockito.<byte[]>any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleCacheUpdateRetry() throws Exception {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
when(impl.digestToStorageKey(key)).thenReturn("bar");
|
||||
when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
|
||||
when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
|
||||
when(impl.updateCAS(Mockito.eq("bar"), Mockito.eq("stuff"), Mockito.<byte[]>any())).thenReturn(false, true);
|
||||
|
||||
impl.updateEntry(key, new HttpCacheUpdateCallback() {
|
||||
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry existing) throws ResourceIOException {
|
||||
return updatedValue;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
verify(impl, Mockito.times(2)).getForUpdateCAS("bar");
|
||||
verify(impl, Mockito.times(2)).getStorageObject("stuff");
|
||||
verify(impl, Mockito.times(2)).updateCAS(Mockito.eq("bar"), Mockito.eq("stuff"), Mockito.<byte[]>any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateFail() throws Exception {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
when(impl.digestToStorageKey(key)).thenReturn("bar");
|
||||
when(impl.getForUpdateCAS("bar")).thenReturn("stuff");
|
||||
when(impl.getStorageObject("stuff")).thenReturn(serialize(key, existingValue));
|
||||
when(impl.updateCAS(Mockito.eq("bar"), Mockito.eq("stuff"), Mockito.<byte[]>any())).thenReturn(false, false, false, true);
|
||||
|
||||
try {
|
||||
impl.updateEntry(key, new HttpCacheUpdateCallback() {
|
||||
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry existing) throws ResourceIOException {
|
||||
return updatedValue;
|
||||
}
|
||||
|
||||
});
|
||||
Assert.fail("HttpCacheUpdateException expected");
|
||||
} catch (final HttpCacheUpdateException ignore) {
|
||||
}
|
||||
|
||||
verify(impl, Mockito.times(3)).getForUpdateCAS("bar");
|
||||
verify(impl, Mockito.times(3)).getStorageObject("stuff");
|
||||
verify(impl, Mockito.times(3)).updateCAS(Mockito.eq("bar"), Mockito.eq("stuff"), Mockito.<byte[]>any());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheStorageEntry;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestByteArrayCacheEntrySerializer {
|
||||
|
||||
private static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
private ByteArrayCacheEntrySerializer impl;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
impl = new ByteArrayCacheEntrySerializer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canSerializeEntriesWithVariantMaps() throws Exception {
|
||||
readWriteVerify(makeCacheEntryWithVariantMap("key"));
|
||||
}
|
||||
|
||||
public void readWriteVerify(final HttpCacheStorageEntry writeEntry) throws Exception {
|
||||
// write the entry
|
||||
final byte[] bytes = impl.serialize(writeEntry);
|
||||
// read the entry
|
||||
final HttpCacheStorageEntry readEntry = impl.deserialize(bytes);
|
||||
// compare
|
||||
assertEquals(readEntry.getKey(), writeEntry.getKey());
|
||||
assertThat(readEntry.getContent(), HttpCacheEntryMatcher.equivalent(writeEntry.getContent()));
|
||||
}
|
||||
|
||||
private HttpCacheStorageEntry makeCacheEntryWithVariantMap(final String key) {
|
||||
final Header[] headers = new Header[5];
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
headers[i] = new BasicHeader("header" + i, "value" + i);
|
||||
}
|
||||
final String body = "Lorem ipsum dolor sit amet";
|
||||
|
||||
final Map<String,String> variantMap = new HashMap<>();
|
||||
variantMap.put("test variant 1","true");
|
||||
variantMap.put("test variant 2","true");
|
||||
final HttpCacheEntry cacheEntry = new HttpCacheEntry(new Date(), new Date(),
|
||||
HttpStatus.SC_OK, headers,
|
||||
new HeapResource(Base64.decodeBase64(body.getBytes(UTF8))), variantMap);
|
||||
|
||||
return new HttpCacheStorageEntry(key, cacheEntry);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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 static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
|
||||
import org.apache.hc.client5.http.cache.Resource;
|
||||
import org.apache.hc.core5.http.Header;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.apache.hc.core5.http.message.BasicHeader;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestHttpCacheEntrySerializers {
|
||||
|
||||
private static final Charset UTF8 = Charset.forName("UTF-8");
|
||||
|
||||
private HttpCacheEntrySerializer impl;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
impl = new DefaultHttpCacheEntrySerializer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canSerializeEntriesWithVariantMaps() throws Exception {
|
||||
readWriteVerify(makeCacheEntryWithVariantMap());
|
||||
}
|
||||
|
||||
public void readWriteVerify(final HttpCacheEntry writeEntry) throws IOException {
|
||||
// write the entry
|
||||
final ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
impl.writeTo(writeEntry, out);
|
||||
|
||||
// read the entry
|
||||
final ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
final HttpCacheEntry readEntry = impl.readFrom(in);
|
||||
|
||||
// compare
|
||||
assertTrue(areEqual(readEntry, writeEntry));
|
||||
}
|
||||
|
||||
private HttpCacheEntry makeCacheEntryWithVariantMap() {
|
||||
final Header[] headers = new Header[5];
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
headers[i] = new BasicHeader("header" + i, "value" + i);
|
||||
}
|
||||
final String body = "Lorem ipsum dolor sit amet";
|
||||
|
||||
final Map<String,String> variantMap = new HashMap<>();
|
||||
variantMap.put("test variant 1","true");
|
||||
variantMap.put("test variant 2","true");
|
||||
final HttpCacheEntry cacheEntry = new HttpCacheEntry(new Date(), new Date(),
|
||||
HttpStatus.SC_OK, headers,
|
||||
new HeapResource(Base64.decodeBase64(body.getBytes(UTF8))), variantMap);
|
||||
|
||||
return cacheEntry;
|
||||
}
|
||||
|
||||
private boolean areEqual(final HttpCacheEntry one, final HttpCacheEntry two) throws IOException {
|
||||
// dates are only stored with second precision, so scrub milliseconds
|
||||
if (!((one.getRequestDate().getTime() / 1000) == (two.getRequestDate()
|
||||
.getTime() / 1000))) {
|
||||
return false;
|
||||
}
|
||||
if (!((one.getResponseDate().getTime() / 1000) == (two
|
||||
.getResponseDate().getTime() / 1000))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final byte[] onesByteArray = resourceToBytes(one.getResource());
|
||||
final byte[] twosByteArray = resourceToBytes(two.getResource());
|
||||
|
||||
if (!Arrays.equals(onesByteArray,twosByteArray)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Header[] oneHeaders = one.getAllHeaders();
|
||||
final Header[] twoHeaders = two.getAllHeaders();
|
||||
if (!(oneHeaders.length == twoHeaders.length)) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < oneHeaders.length; i++) {
|
||||
if (!oneHeaders[i].getName().equals(twoHeaders[i].getName())) {
|
||||
return false;
|
||||
}
|
||||
if (!oneHeaders[i].getValue().equals(twoHeaders[i].getValue())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private byte[] resourceToBytes(final Resource res) throws IOException {
|
||||
final InputStream inputStream = res.getInputStream();
|
||||
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
int readBytes;
|
||||
final byte[] bytes = new byte[8096];
|
||||
while ((readBytes = inputStream.read(bytes)) > 0) {
|
||||
outputStream.write(bytes, 0, readBytes);
|
||||
}
|
||||
|
||||
final byte[] byteData = outputStream.toByteArray();
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
return byteData;
|
||||
}
|
||||
}
|
|
@ -1,247 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.ehcache;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntrySerializer;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheUpdateCallback;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
|
||||
import org.apache.hc.client5.http.impl.cache.CacheConfig;
|
||||
import org.apache.hc.client5.http.impl.cache.HttpTestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import net.sf.ehcache.Ehcache;
|
||||
import net.sf.ehcache.Element;
|
||||
|
||||
@SuppressWarnings("boxing") // test code
|
||||
public class TestEhcacheHttpCacheStorage extends TestCase {
|
||||
|
||||
private Ehcache mockCache;
|
||||
private EhcacheHttpCacheStorage impl;
|
||||
private HttpCacheEntrySerializer mockSerializer;
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
mockCache = mock(Ehcache.class);
|
||||
final CacheConfig config = CacheConfig.custom().setMaxUpdateRetries(1).build();
|
||||
mockSerializer = mock(HttpCacheEntrySerializer.class);
|
||||
impl = new EhcacheHttpCacheStorage(mockCache, config, mockSerializer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCachePut() throws IOException {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
final Element e = new Element(key, new byte[]{});
|
||||
|
||||
impl.putEntry(key, value);
|
||||
|
||||
verify(mockSerializer).writeTo(same(value), isA(OutputStream.class));
|
||||
verify(mockCache).put(e);;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheGetNullEntry() throws IOException {
|
||||
final String key = "foo";
|
||||
|
||||
when(mockCache.get(key)).thenReturn(null);
|
||||
|
||||
final HttpCacheEntry resultingEntry = impl.getEntry(key);
|
||||
|
||||
verify(mockCache).get(key);
|
||||
|
||||
assertNull(resultingEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheGet() throws IOException {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry cachedValue = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
final Element element = new Element(key, new byte[]{});
|
||||
|
||||
when(mockCache.get(key))
|
||||
.thenReturn(element);
|
||||
when(mockSerializer.readFrom(isA(InputStream.class)))
|
||||
.thenReturn(cachedValue);
|
||||
|
||||
final HttpCacheEntry resultingEntry = impl.getEntry(key);
|
||||
|
||||
verify(mockCache).get(key);
|
||||
verify(mockSerializer).readFrom(isA(InputStream.class));
|
||||
|
||||
assertSame(cachedValue, resultingEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheRemove() {
|
||||
final String key = "foo";
|
||||
|
||||
when(mockCache.remove(key)).thenReturn(true);
|
||||
|
||||
impl.removeEntry(key);
|
||||
verify(mockCache).remove(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateNullEntry() throws IOException, HttpCacheUpdateException {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
final Element element = new Element(key, new byte[]{});
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback(){
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry old){
|
||||
assertNull(old);
|
||||
return updatedValue;
|
||||
}
|
||||
};
|
||||
|
||||
// get empty old entry
|
||||
when(mockCache.get(key)).thenReturn(null);
|
||||
|
||||
// put new entry
|
||||
mockSerializer.writeTo(same(updatedValue), isA(OutputStream.class));
|
||||
|
||||
impl.updateEntry(key, callback);
|
||||
|
||||
verify(mockCache).get(key);
|
||||
verify(mockSerializer).writeTo(same(updatedValue), isA(OutputStream.class));
|
||||
verify(mockCache).put(element);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheUpdate() throws IOException, HttpCacheUpdateException {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
final Element existingElement = new Element(key, new byte[]{});
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback(){
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry old){
|
||||
assertEquals(existingValue, old);
|
||||
return updatedValue;
|
||||
}
|
||||
};
|
||||
|
||||
// get existing old entry
|
||||
when(mockCache.get(key)).thenReturn(existingElement);
|
||||
when(mockSerializer.readFrom(isA(InputStream.class))).thenReturn(existingValue);
|
||||
|
||||
// processChallenge
|
||||
mockSerializer.writeTo(same(updatedValue), isA(OutputStream.class));
|
||||
when(mockCache.replace(same(existingElement), isA(Element.class))).thenReturn(true);
|
||||
|
||||
impl.updateEntry(key, callback);
|
||||
|
||||
verify(mockCache).get(key);
|
||||
verify(mockSerializer).readFrom(isA(InputStream.class));
|
||||
verify(mockSerializer).writeTo(same(updatedValue), isA(OutputStream.class));
|
||||
verify(mockCache).replace(same(existingElement), isA(Element.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleCacheUpdateRetry() throws IOException, HttpCacheUpdateException {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
final Element existingElement = new Element(key, new byte[]{});
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback(){
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry old){
|
||||
assertEquals(existingValue, old);
|
||||
return updatedValue;
|
||||
}
|
||||
};
|
||||
|
||||
// get existing old entry, will happen twice
|
||||
when(mockCache.get(key)).thenReturn(existingElement);
|
||||
when(mockSerializer.readFrom(isA(InputStream.class))).thenReturn(existingValue);
|
||||
|
||||
// Fail first and then succeed
|
||||
when(mockCache.replace(same(existingElement), isA(Element.class))).thenReturn(false).thenReturn(true);
|
||||
|
||||
impl.updateEntry(key, callback);
|
||||
|
||||
verify(mockCache, times(2)).get(key);
|
||||
verify(mockSerializer, times(2)).readFrom(isA(InputStream.class));
|
||||
verify(mockSerializer, times(2)).writeTo(same(updatedValue), isA(OutputStream.class));
|
||||
verify(mockCache, times(2)).replace(same(existingElement), isA(Element.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateFail() throws IOException {
|
||||
final String key = "foo";
|
||||
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
final Element existingElement = new Element(key, new byte[]{});
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback(){
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry old){
|
||||
assertEquals(existingValue, old);
|
||||
return updatedValue;
|
||||
}
|
||||
};
|
||||
|
||||
// get existing old entry
|
||||
when(mockCache.get(key)).thenReturn(existingElement);
|
||||
when(mockSerializer.readFrom(isA(InputStream.class))).thenReturn(existingValue);
|
||||
|
||||
// processChallenge but fail
|
||||
when(mockCache.replace(same(existingElement), isA(Element.class))).thenReturn(false);
|
||||
|
||||
try{
|
||||
impl.updateEntry(key, callback);
|
||||
fail("Expected HttpCacheUpdateException");
|
||||
} catch (final HttpCacheUpdateException e) { }
|
||||
|
||||
verify(mockCache, times(2)).get(key);
|
||||
verify(mockSerializer, times(2)).readFrom(isA(InputStream.class));
|
||||
verify(mockSerializer, times(2)).writeTo(same(updatedValue), isA(OutputStream.class));
|
||||
verify(mockCache, times(2)).replace(same(existingElement), isA(Element.class));
|
||||
}
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.ehcache;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheStorage;
|
||||
import org.apache.hc.client5.http.classic.ExecChain;
|
||||
import org.apache.hc.client5.http.impl.cache.CacheConfig;
|
||||
import org.apache.hc.client5.http.impl.cache.CachingExec;
|
||||
import org.apache.hc.client5.http.impl.cache.HeapResourceFactory;
|
||||
import org.apache.hc.client5.http.impl.cache.TestProtocolRequirements;
|
||||
import org.easymock.EasyMock;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.config.CacheConfiguration;
|
||||
import net.sf.ehcache.config.Configuration;
|
||||
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
|
||||
|
||||
public class TestEhcacheProtocolRequirements extends TestProtocolRequirements{
|
||||
|
||||
protected final String TEST_EHCACHE_NAME = "TestEhcacheProtocolRequirements-cache";
|
||||
|
||||
protected static CacheManager CACHE_MANAGER;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpGlobal() {
|
||||
final Configuration config = new Configuration();
|
||||
config.addDefaultCache(
|
||||
new CacheConfiguration("default", Integer.MAX_VALUE)
|
||||
.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LFU)
|
||||
.overflowToDisk(false));
|
||||
CACHE_MANAGER = CacheManager.create(config);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() {
|
||||
super.setUp();
|
||||
config = CacheConfig.custom().setMaxObjectSize(MAX_BYTES).build();
|
||||
|
||||
if (CACHE_MANAGER.cacheExists(TEST_EHCACHE_NAME)){
|
||||
CACHE_MANAGER.removeCache(TEST_EHCACHE_NAME);
|
||||
}
|
||||
CACHE_MANAGER.addCache(TEST_EHCACHE_NAME);
|
||||
final HttpCacheStorage storage = new EhcacheHttpCacheStorage(CACHE_MANAGER.getCache(TEST_EHCACHE_NAME));
|
||||
mockExecChain = EasyMock.createNiceMock(ExecChain.class);
|
||||
|
||||
impl = new CachingExec(new HeapResourceFactory(), storage, config);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown(){
|
||||
CACHE_MANAGER.removeCache(TEST_EHCACHE_NAME);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDownGlobal(){
|
||||
CACHE_MANAGER.shutdown();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.memcached;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.impl.cache.HttpTestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestMemcachedCacheEntryFactoryImpl {
|
||||
|
||||
@Test
|
||||
public void createsMemcachedCacheEntryImpls() {
|
||||
final String key = "key";
|
||||
final HttpCacheEntry entry = HttpTestUtils.makeCacheEntry();
|
||||
final MemcachedCacheEntryFactoryImpl impl = new MemcachedCacheEntryFactoryImpl();
|
||||
final MemcachedCacheEntry result = impl.getMemcachedCacheEntry(key, entry);
|
||||
assertNotNull(result);
|
||||
assertSame(key, result.getStorageKey());
|
||||
assertSame(entry, result.getHttpCacheEntry());
|
||||
}
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.memcached;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.impl.cache.DefaultHttpCacheEntrySerializer;
|
||||
import org.apache.hc.client5.http.impl.cache.HttpTestUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestMemcachedCacheEntryImpl {
|
||||
|
||||
private MemcachedCacheEntryImpl impl;
|
||||
private HttpCacheEntry entry;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
entry = HttpTestUtils.makeCacheEntry();
|
||||
impl = new MemcachedCacheEntryImpl("foo", entry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canBeCreatedEmpty() {
|
||||
impl = new MemcachedCacheEntryImpl();
|
||||
assertNull(impl.getStorageKey());
|
||||
assertNull(impl.getHttpCacheEntry());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canBeSerialized() throws Exception {
|
||||
final byte[] bytes = impl.toByteArray();
|
||||
assertNotNull(bytes);
|
||||
assertTrue(bytes.length > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void knowsItsCacheKey() {
|
||||
assertEquals("foo", impl.getStorageKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void knowsItsCacheEntry() {
|
||||
assertEquals(entry, impl.getHttpCacheEntry());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canBeReconstitutedFromByteArray() throws Exception {
|
||||
final String key = impl.getStorageKey();
|
||||
final HttpCacheEntry entry1 = impl.getHttpCacheEntry();
|
||||
final byte[] bytes = impl.toByteArray();
|
||||
impl = new MemcachedCacheEntryImpl();
|
||||
impl.set(bytes);
|
||||
|
||||
assertEquals(key, impl.getStorageKey());
|
||||
assertEquivalent(entry1, impl.getHttpCacheEntry());
|
||||
}
|
||||
|
||||
@Test(expected=MemcachedSerializationException.class)
|
||||
public void cannotReconstituteFromGarbage() throws Exception {
|
||||
impl = new MemcachedCacheEntryImpl();
|
||||
final byte[] bytes = HttpTestUtils.getRandomBytes(128);
|
||||
impl.set(bytes);
|
||||
}
|
||||
|
||||
private void assertEquivalent(final HttpCacheEntry entry,
|
||||
final HttpCacheEntry resultEntry) throws IOException {
|
||||
/* Ugh. Implementing HttpCacheEntry#equals is problematic
|
||||
* due to the Resource response body (may cause unexpected
|
||||
* I/O to users). Checking that two entries
|
||||
* serialize to the same bytes seems simpler, on the whole,
|
||||
* (while still making for a somewhat yucky test). At
|
||||
* least we encapsulate it off here in its own method so
|
||||
* the test that uses it remains clear.
|
||||
*/
|
||||
final DefaultHttpCacheEntrySerializer ser = new DefaultHttpCacheEntrySerializer();
|
||||
final ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
|
||||
ser.writeTo(entry, bos1);
|
||||
final byte[] bytes1 = bos1.toByteArray();
|
||||
final ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
|
||||
ser.writeTo(resultEntry, bos2);
|
||||
final byte[] bytes2 = bos2.toByteArray();
|
||||
assertEquals(bytes1.length, bytes2.length);
|
||||
for(int i = 0; i < bytes1.length; i++) {
|
||||
assertEquals(bytes1[i], bytes2[i]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,603 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* 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.memcached;
|
||||
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.hc.client5.http.cache.HttpCacheEntry;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheUpdateCallback;
|
||||
import org.apache.hc.client5.http.cache.HttpCacheUpdateException;
|
||||
import org.apache.hc.client5.http.impl.cache.CacheConfig;
|
||||
import org.apache.hc.client5.http.impl.cache.HttpTestUtils;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import net.spy.memcached.CASResponse;
|
||||
import net.spy.memcached.CASValue;
|
||||
import net.spy.memcached.MemcachedClientIF;
|
||||
import net.spy.memcached.OperationTimeoutException;
|
||||
|
||||
public class TestMemcachedHttpCacheStorage extends TestCase {
|
||||
private MemcachedHttpCacheStorage impl;
|
||||
private MemcachedClientIF mockMemcachedClient;
|
||||
private KeyHashingScheme mockKeyHashingScheme;
|
||||
private MemcachedCacheEntryFactory mockMemcachedCacheEntryFactory;
|
||||
private MemcachedCacheEntry mockMemcachedCacheEntry;
|
||||
private MemcachedCacheEntry mockMemcachedCacheEntry2;
|
||||
private MemcachedCacheEntry mockMemcachedCacheEntry3;
|
||||
private MemcachedCacheEntry mockMemcachedCacheEntry4;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mockMemcachedClient = mock(MemcachedClientIF.class);
|
||||
mockKeyHashingScheme = mock(KeyHashingScheme.class);
|
||||
mockMemcachedCacheEntryFactory = mock(MemcachedCacheEntryFactory.class);
|
||||
mockMemcachedCacheEntry = mock(MemcachedCacheEntry.class);
|
||||
mockMemcachedCacheEntry2 = mock(MemcachedCacheEntry.class);
|
||||
mockMemcachedCacheEntry3 = mock(MemcachedCacheEntry.class);
|
||||
mockMemcachedCacheEntry4 = mock(MemcachedCacheEntry.class);
|
||||
final CacheConfig config = CacheConfig.custom().setMaxUpdateRetries(1).build();
|
||||
impl = new MemcachedHttpCacheStorage(mockMemcachedClient, config,
|
||||
mockMemcachedCacheEntryFactory, mockKeyHashingScheme);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulCachePut() throws IOException {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
|
||||
final byte[] serialized = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
|
||||
.thenReturn(mockMemcachedCacheEntry);
|
||||
when(mockMemcachedCacheEntry.toByteArray())
|
||||
.thenReturn(serialized);
|
||||
when(mockKeyHashingScheme.hash(url))
|
||||
.thenReturn(key);
|
||||
when(mockMemcachedClient.set(key, 0, serialized))
|
||||
.thenReturn(null);
|
||||
|
||||
impl.putEntry(url, value);
|
||||
verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, value);
|
||||
verify(mockMemcachedCacheEntry).toByteArray();
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).set(key, 0, serialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCachePutFailsSilentlyWhenWeCannotHashAKey() throws IOException {
|
||||
final String url = "foo";
|
||||
final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
|
||||
final byte[] serialized = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
|
||||
.thenReturn(mockMemcachedCacheEntry);
|
||||
when(mockMemcachedCacheEntry.toByteArray())
|
||||
.thenReturn(serialized);
|
||||
when(mockKeyHashingScheme.hash(url))
|
||||
.thenThrow(new MemcachedKeyHashingException(new Exception()));
|
||||
|
||||
impl.putEntry(url, value);
|
||||
|
||||
verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, value);
|
||||
verify(mockMemcachedCacheEntry).toByteArray();
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
}
|
||||
|
||||
public void testThrowsIOExceptionWhenMemcachedPutTimesOut() throws Exception {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
|
||||
final byte[] serialized = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
|
||||
.thenReturn(mockMemcachedCacheEntry);
|
||||
when(mockMemcachedCacheEntry.toByteArray())
|
||||
.thenReturn(serialized);
|
||||
when(mockKeyHashingScheme.hash(url))
|
||||
.thenReturn(key);
|
||||
when(mockMemcachedClient.set(key, 0, serialized))
|
||||
.thenThrow(new OperationTimeoutException("timed out"));
|
||||
|
||||
try {
|
||||
impl.putEntry(url, value);
|
||||
fail("should have thrown exception");
|
||||
} catch (final IOException expected) {
|
||||
}
|
||||
|
||||
verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, value);
|
||||
verify(mockMemcachedCacheEntry).toByteArray();
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).set(key, 0, serialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCachePutThrowsIOExceptionIfCannotSerializeEntry() throws Exception {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
final HttpCacheEntry value = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, value))
|
||||
.thenReturn(mockMemcachedCacheEntry);
|
||||
when(mockMemcachedCacheEntry.toByteArray())
|
||||
.thenThrow(new MemcachedSerializationException(new Exception()));
|
||||
|
||||
try {
|
||||
impl.putEntry(url, value);
|
||||
fail("should have thrown exception");
|
||||
} catch (final IOException expected) {
|
||||
|
||||
}
|
||||
|
||||
verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, value);
|
||||
verify(mockMemcachedCacheEntry).toByteArray();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulCacheGet() throws Exception {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
final byte[] serialized = HttpTestUtils.getRandomBytes(128);
|
||||
final HttpCacheEntry cacheEntry = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.get(key)).thenReturn(serialized);
|
||||
when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
|
||||
.thenReturn(mockMemcachedCacheEntry);
|
||||
when(mockMemcachedCacheEntry.getStorageKey()).thenReturn(url);
|
||||
when(mockMemcachedCacheEntry.getHttpCacheEntry()).thenReturn(cacheEntry);
|
||||
|
||||
final HttpCacheEntry resultingEntry = impl.getEntry(url);
|
||||
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).get(key);
|
||||
verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
|
||||
verify(mockMemcachedCacheEntry).set(serialized);
|
||||
verify(mockMemcachedCacheEntry).getStorageKey();
|
||||
verify(mockMemcachedCacheEntry).getHttpCacheEntry();
|
||||
|
||||
assertSame(cacheEntry, resultingEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatsNoneByteArrayFromMemcachedAsCacheMiss() throws Exception {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.get(key)).thenReturn(new Object());
|
||||
|
||||
final HttpCacheEntry resultingEntry = impl.getEntry(url);
|
||||
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).get(key);
|
||||
|
||||
assertNull(resultingEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatsNullFromMemcachedAsCacheMiss() throws Exception {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.get(key)).thenReturn(null);
|
||||
|
||||
final HttpCacheEntry resultingEntry = impl.getEntry(url);
|
||||
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).get(key);
|
||||
|
||||
assertNull(resultingEntry);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatsAsCacheMissIfCannotReconstituteEntry() throws Exception {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
final byte[] serialized = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.get(key)).thenReturn(serialized);
|
||||
when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
|
||||
.thenReturn(mockMemcachedCacheEntry);
|
||||
doThrow(new MemcachedSerializationException(new Exception())).when(mockMemcachedCacheEntry).set(serialized);
|
||||
|
||||
assertNull(impl.getEntry(url));
|
||||
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).get(key);
|
||||
verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
|
||||
verify(mockMemcachedCacheEntry).set(serialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTreatsAsCacheMissIfCantHashStorageKey() throws Exception {
|
||||
final String url = "foo";
|
||||
|
||||
when(mockKeyHashingScheme.hash(url)).thenThrow(new MemcachedKeyHashingException(new Exception()));
|
||||
|
||||
assertNull(impl.getEntry(url));
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testThrowsIOExceptionIfMemcachedTimesOutOnGet() {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.get(key))
|
||||
.thenThrow(new OperationTimeoutException(""));
|
||||
|
||||
try {
|
||||
impl.getEntry(url);
|
||||
fail("should have thrown exception");
|
||||
} catch (final IOException expected) {
|
||||
}
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).get(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheRemove() throws IOException {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.delete(key)).thenReturn(null);
|
||||
|
||||
impl.removeEntry(url);
|
||||
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).delete(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheRemoveHandlesKeyHashingFailure() throws IOException {
|
||||
final String url = "foo";
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(null);
|
||||
impl.removeEntry(url);
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheRemoveThrowsIOExceptionOnMemcachedTimeout() {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.delete(key))
|
||||
.thenThrow(new OperationTimeoutException(""));
|
||||
|
||||
try {
|
||||
impl.removeEntry(url);
|
||||
fail("should have thrown exception");
|
||||
} catch (final IOException expected) {
|
||||
}
|
||||
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).delete(key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateCanUpdateNullEntry() throws IOException,
|
||||
HttpCacheUpdateException {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
final byte[] serialized = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry old) {
|
||||
assertNull(old);
|
||||
return updatedValue;
|
||||
}
|
||||
};
|
||||
|
||||
// get empty old entry
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.gets(key)).thenReturn(null);
|
||||
when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
|
||||
.thenReturn(mockMemcachedCacheEntry);
|
||||
when(mockMemcachedCacheEntry.toByteArray()).thenReturn(serialized);
|
||||
when(
|
||||
mockMemcachedClient.set(key, 0,
|
||||
serialized)).thenReturn(null);
|
||||
|
||||
impl.updateEntry(url, callback);
|
||||
|
||||
verify(mockKeyHashingScheme, times(2)).hash(url);
|
||||
verify(mockMemcachedClient).gets(key);
|
||||
verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue);
|
||||
verify(mockMemcachedCacheEntry).toByteArray();
|
||||
verify(mockMemcachedClient).set(key, 0, serialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateOverwritesNonMatchingHashCollision() throws IOException,
|
||||
HttpCacheUpdateException {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
|
||||
final CASValue<Object> casValue = new CASValue<Object>(-1, oldBytes);
|
||||
final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry old) {
|
||||
assertNull(old);
|
||||
return updatedValue;
|
||||
}
|
||||
};
|
||||
|
||||
// get empty old entry
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.gets(key)).thenReturn(casValue);
|
||||
when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
|
||||
.thenReturn(mockMemcachedCacheEntry);
|
||||
when(mockMemcachedCacheEntry.getStorageKey()).thenReturn("not" + url);
|
||||
|
||||
when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
|
||||
.thenReturn(mockMemcachedCacheEntry2);
|
||||
when(mockMemcachedCacheEntry2.toByteArray()).thenReturn(newBytes);
|
||||
when(
|
||||
mockMemcachedClient.set(key, 0,
|
||||
newBytes)).thenReturn(null);
|
||||
|
||||
impl.updateEntry(url, callback);
|
||||
|
||||
verify(mockKeyHashingScheme, times(2)).hash(url);
|
||||
verify(mockMemcachedClient).gets(key);
|
||||
verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
|
||||
verify(mockMemcachedCacheEntry).getStorageKey();
|
||||
verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue);
|
||||
verify(mockMemcachedCacheEntry2).toByteArray();
|
||||
verify(mockMemcachedClient).set(key, 0, newBytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateCanUpdateExistingEntry() throws IOException,
|
||||
HttpCacheUpdateException {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
|
||||
final CASValue<Object> casValue = new CASValue<Object>(1, oldBytes);
|
||||
final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry old) {
|
||||
assertSame(existingValue, old);
|
||||
return updatedValue;
|
||||
}
|
||||
};
|
||||
|
||||
// get empty old entry
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.gets(key)).thenReturn(casValue);
|
||||
when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
|
||||
.thenReturn(mockMemcachedCacheEntry);
|
||||
when(mockMemcachedCacheEntry.getStorageKey()).thenReturn(url);
|
||||
when(mockMemcachedCacheEntry.getHttpCacheEntry()).thenReturn(existingValue);
|
||||
|
||||
when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
|
||||
.thenReturn(mockMemcachedCacheEntry2);
|
||||
when(mockMemcachedCacheEntry2.toByteArray()).thenReturn(newBytes);
|
||||
|
||||
when(
|
||||
mockMemcachedClient.cas(key, casValue.getCas(),
|
||||
newBytes)).thenReturn(CASResponse.OK);
|
||||
|
||||
impl.updateEntry(url, callback);
|
||||
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).gets(key);
|
||||
verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
|
||||
verify(mockMemcachedCacheEntry).getStorageKey();
|
||||
verify(mockMemcachedCacheEntry).getHttpCacheEntry();
|
||||
verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue);
|
||||
verify(mockMemcachedCacheEntry2).toByteArray();
|
||||
verify(mockMemcachedClient).cas(key, casValue.getCas(), newBytes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateThrowsExceptionsIfCASFailsEnoughTimes() throws IOException {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
final byte[] oldBytes = HttpTestUtils.getRandomBytes(128);
|
||||
final CASValue<Object> casValue = new CASValue<Object>(1, oldBytes);
|
||||
final byte[] newBytes = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
final CacheConfig config = CacheConfig.custom().setMaxUpdateRetries(0).build();
|
||||
impl = new MemcachedHttpCacheStorage(mockMemcachedClient, config,
|
||||
mockMemcachedCacheEntryFactory, mockKeyHashingScheme);
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry old) {
|
||||
assertSame(existingValue, old);
|
||||
return updatedValue;
|
||||
}
|
||||
};
|
||||
|
||||
// get empty old entry
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.gets(key)).thenReturn(casValue);
|
||||
when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
|
||||
.thenReturn(mockMemcachedCacheEntry);
|
||||
when(mockMemcachedCacheEntry.getStorageKey()).thenReturn(url);
|
||||
when(mockMemcachedCacheEntry.getHttpCacheEntry()).thenReturn(existingValue);
|
||||
|
||||
when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue))
|
||||
.thenReturn(mockMemcachedCacheEntry2);
|
||||
when(mockMemcachedCacheEntry2.toByteArray()).thenReturn(newBytes);
|
||||
|
||||
when(
|
||||
mockMemcachedClient.cas(key, casValue.getCas(),
|
||||
newBytes)).thenReturn(CASResponse.EXISTS);
|
||||
|
||||
try {
|
||||
impl.updateEntry(url, callback);
|
||||
fail("should have thrown exception");
|
||||
} catch (final HttpCacheUpdateException expected) {
|
||||
}
|
||||
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).gets(key);
|
||||
verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
|
||||
verify(mockMemcachedCacheEntry).getStorageKey();
|
||||
verify(mockMemcachedCacheEntry).getHttpCacheEntry();
|
||||
verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue);
|
||||
verify(mockMemcachedCacheEntry2).toByteArray();
|
||||
verify(mockMemcachedClient).cas(key, casValue.getCas(), newBytes);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCacheUpdateCanUpdateExistingEntryWithRetry() throws IOException,
|
||||
HttpCacheUpdateException {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
final HttpCacheEntry existingValue = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry existingValue2 = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
final HttpCacheEntry updatedValue2 = HttpTestUtils.makeCacheEntry();
|
||||
final byte[] oldBytes2 = HttpTestUtils.getRandomBytes(128);
|
||||
final CASValue<Object> casValue2 = new CASValue<Object>(2, oldBytes2);
|
||||
final byte[] newBytes2 = HttpTestUtils.getRandomBytes(128);
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry old) {
|
||||
if (old == existingValue) {
|
||||
return updatedValue;
|
||||
}
|
||||
assertSame(existingValue2, old);
|
||||
return updatedValue2;
|
||||
}
|
||||
};
|
||||
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
|
||||
// take two
|
||||
when(mockMemcachedClient.gets(key)).thenReturn(casValue2);
|
||||
when(mockMemcachedCacheEntryFactory.getUnsetCacheEntry())
|
||||
.thenReturn(mockMemcachedCacheEntry3);
|
||||
when(mockMemcachedCacheEntry3.getStorageKey()).thenReturn(url);
|
||||
when(mockMemcachedCacheEntry3.getHttpCacheEntry()).thenReturn(existingValue2);
|
||||
|
||||
when(mockMemcachedCacheEntryFactory.getMemcachedCacheEntry(url, updatedValue2))
|
||||
.thenReturn(mockMemcachedCacheEntry4);
|
||||
when(mockMemcachedCacheEntry4.toByteArray()).thenReturn(newBytes2);
|
||||
|
||||
when(
|
||||
mockMemcachedClient.cas(key, casValue2.getCas(),
|
||||
newBytes2)).thenReturn(CASResponse.OK);
|
||||
|
||||
impl.updateEntry(url, callback);
|
||||
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).gets(key);
|
||||
verify(mockMemcachedCacheEntryFactory).getUnsetCacheEntry();
|
||||
|
||||
verify(mockMemcachedCacheEntry3).set(oldBytes2);
|
||||
verify(mockMemcachedCacheEntry3).getStorageKey();
|
||||
verify(mockMemcachedCacheEntry3).getHttpCacheEntry();
|
||||
verify(mockMemcachedCacheEntryFactory).getMemcachedCacheEntry(url, updatedValue2);
|
||||
verify(mockMemcachedCacheEntry4).toByteArray();
|
||||
verify(mockMemcachedClient).cas(key, casValue2.getCas(), newBytes2);
|
||||
|
||||
verifyNoMoreInteractions(mockMemcachedClient);
|
||||
verifyNoMoreInteractions(mockKeyHashingScheme);
|
||||
verifyNoMoreInteractions(mockMemcachedCacheEntry);
|
||||
verifyNoMoreInteractions(mockMemcachedCacheEntry2);
|
||||
verifyNoMoreInteractions(mockMemcachedCacheEntry3);
|
||||
verifyNoMoreInteractions(mockMemcachedCacheEntry4);
|
||||
verifyNoMoreInteractions(mockMemcachedCacheEntryFactory);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpdateThrowsIOExceptionIfMemcachedTimesOut() throws HttpCacheUpdateException {
|
||||
final String url = "foo";
|
||||
final String key = "key";
|
||||
final HttpCacheEntry updatedValue = HttpTestUtils.makeCacheEntry();
|
||||
|
||||
final HttpCacheUpdateCallback callback = new HttpCacheUpdateCallback() {
|
||||
@Override
|
||||
public HttpCacheEntry update(final HttpCacheEntry old) {
|
||||
assertNull(old);
|
||||
return updatedValue;
|
||||
}
|
||||
};
|
||||
|
||||
// get empty old entry
|
||||
when(mockKeyHashingScheme.hash(url)).thenReturn(key);
|
||||
when(mockMemcachedClient.gets(key))
|
||||
.thenThrow(new OperationTimeoutException(""));
|
||||
|
||||
try {
|
||||
impl.updateEntry(url, callback);
|
||||
fail("should have thrown exception");
|
||||
} catch (final IOException expected) {
|
||||
}
|
||||
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
verify(mockMemcachedClient).gets(key);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected=HttpCacheUpdateException.class)
|
||||
public void testThrowsExceptionOnUpdateIfCannotHashStorageKey() throws Exception {
|
||||
final String url = "foo";
|
||||
|
||||
when(mockKeyHashingScheme.hash(url))
|
||||
.thenThrow(new MemcachedKeyHashingException(new Exception()));
|
||||
|
||||
try {
|
||||
impl.updateEntry(url, null);
|
||||
fail("should have thrown exception");
|
||||
} catch (final HttpCacheUpdateException expected) {
|
||||
}
|
||||
|
||||
verify(mockKeyHashingScheme).hash(url);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue