/*
* Copyright (c) 2014-2015 Spotify AB
*
* Licensed 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.
*/
package com.spotify.folsom.client.ascii;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.spotify.folsom.AsciiMemcacheClient;
import com.spotify.folsom.ConnectionChangeListener;
import com.spotify.folsom.GetResult;
import com.spotify.folsom.MemcacheStatus;
import com.spotify.folsom.Metrics;
import com.spotify.folsom.RawMemcacheClient;
import com.spotify.folsom.Transcoder;
import com.spotify.folsom.client.MemcacheEncoder;
import com.spotify.folsom.client.TransformerUtil;
import com.spotify.folsom.client.Utils;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* The default implementation of {@link com.spotify.folsom.AsciiMemcacheClient}
*
* @param <V> The value type for all operations
*/
public class DefaultAsciiMemcacheClient<V> implements AsciiMemcacheClient<V> {
private final RawMemcacheClient rawMemcacheClient;
private final Metrics metrics;
private final Transcoder<V> valueTranscoder;
private final TransformerUtil<V> transformerUtil;
private final Charset charset;
public DefaultAsciiMemcacheClient(final RawMemcacheClient rawMemcacheClient,
final Metrics metrics,
final Transcoder<V> valueTranscoder,
Charset charset) {
this.rawMemcacheClient = rawMemcacheClient;
this.metrics = metrics;
this.valueTranscoder = valueTranscoder;
this.charset = charset;
this.transformerUtil = new TransformerUtil<>(valueTranscoder);
}
@Override
public ListenableFuture<MemcacheStatus> set(final String key, final V value, final int ttl) {
checkNotNull(value);
final byte[] valueBytes = valueTranscoder.encode(value);
SetRequest request = SetRequest.create(
SetRequest.Operation.SET, key, charset, valueBytes, ttl);
ListenableFuture<MemcacheStatus> future = rawMemcacheClient.send(request);
metrics.measureSetFuture(future);
return future;
}
@Override
public ListenableFuture<MemcacheStatus> set(String key, V value, int ttl, long cas) {
checkNotNull(value);
final byte[] valueBytes = valueTranscoder.encode(value);
SetRequest request = SetRequest.casSet(key, charset, valueBytes, ttl, cas);
ListenableFuture<MemcacheStatus> future = rawMemcacheClient.send(request);
metrics.measureSetFuture(future);
return future;
}
@Override
public ListenableFuture<MemcacheStatus> delete(final String key) {
DeleteRequest request = new DeleteRequest(key, charset);
ListenableFuture<MemcacheStatus> future = rawMemcacheClient.send(request);
metrics.measureDeleteFuture(future);
return future;
}
@Override
public ListenableFuture<MemcacheStatus> add(String key, V value, int ttl) {
checkNotNull(value);
final byte[] valueBytes = valueTranscoder.encode(value);
SetRequest request = SetRequest.create(
SetRequest.Operation.ADD, key, charset, valueBytes, ttl);
ListenableFuture<MemcacheStatus> future = rawMemcacheClient.send(request);
metrics.measureSetFuture(future);
return future;
}
@Override
public ListenableFuture<MemcacheStatus> replace(String key, V value, int ttl) {
checkNotNull(value);
final byte[] valueBytes = valueTranscoder.encode(value);
SetRequest request = SetRequest.create(
SetRequest.Operation.REPLACE, key, charset, valueBytes, ttl);
ListenableFuture<MemcacheStatus> future = rawMemcacheClient.send(request);
metrics.measureSetFuture(future);
return future;
}
@Override
public ListenableFuture<MemcacheStatus> append(String key, V value) {
checkNotNull(value);
final byte[] valueBytes = valueTranscoder.encode(value);
SetRequest request = SetRequest.create(
SetRequest.Operation.APPEND, key, charset, valueBytes, 0);
ListenableFuture<MemcacheStatus> future = rawMemcacheClient.send(request);
metrics.measureSetFuture(future);
return future;
}
@Override
public ListenableFuture<MemcacheStatus> prepend(String key, V value) {
checkNotNull(value);
final byte[] valueBytes = valueTranscoder.encode(value);
SetRequest request = SetRequest.create(
SetRequest.Operation.PREPEND, key, charset, valueBytes, 0);
ListenableFuture<MemcacheStatus> future = rawMemcacheClient.send(request);
metrics.measureSetFuture(future);
return future;
}
@Override
public ListenableFuture<Long> incr(String key, long by) {
IncrRequest request = IncrRequest.createIncr(key, charset, by);
ListenableFuture<Long> future = rawMemcacheClient.send(request);
metrics.measureIncrDecrFuture(future);
return future;
}
@Override
public ListenableFuture<Long> decr(String key, long by) {
IncrRequest request = IncrRequest.createDecr(key, charset, by);
ListenableFuture<Long> future = rawMemcacheClient.send(request);
metrics.measureIncrDecrFuture(future);
return future;
}
@Override
public ListenableFuture<V> get(final String key) {
return transformerUtil.unwrap(get(key, false));
}
@Override
public ListenableFuture<GetResult<V>> casGet(final String key) {
return get(key, true);
}
private ListenableFuture<GetResult<V>> get(final String key, final boolean withCas) {
final ListenableFuture<GetResult<byte[]>> future =
rawMemcacheClient.send(new GetRequest(key, charset, withCas));
metrics.measureGetFuture(future);
return transformerUtil.decode(future);
}
@Override
public ListenableFuture<List<V>> get(final List<String> keys) {
return transformerUtil.unwrapList(multiget(keys, false));
}
@Override
public ListenableFuture<List<GetResult<V>>> casGet(final List<String> keys) {
return multiget(keys, true);
}
@Override
public ListenableFuture<MemcacheStatus> touch(String key, int ttl) {
TouchRequest request = new TouchRequest(key, charset, ttl);
ListenableFuture<MemcacheStatus> future = rawMemcacheClient.send(request);
metrics.measureTouchFuture(future);
return future;
}
private ListenableFuture<List<GetResult<V>>> multiget(List<String> keys, boolean withCas) {
final int size = keys.size();
if (size == 0) {
return Futures.immediateFuture(Collections.<GetResult<V>>emptyList());
}
final List<List<String>> keyPartition =
Lists.partition(keys, MemcacheEncoder.MAX_MULTIGET_SIZE);
final List<ListenableFuture<List<GetResult<byte[]>>>> futureList =
new ArrayList<>(keyPartition.size());
for (final List<String> part : keyPartition) {
MultigetRequest request = MultigetRequest.create(part, charset, withCas);
futureList.add(rawMemcacheClient.send(request));
}
final ListenableFuture<List<GetResult<byte[]>>> future =
Utils.transform(Futures.allAsList(futureList), Utils.<GetResult<byte[]>>flatten());
metrics.measureMultigetFuture(future);
return transformerUtil.decodeList(future);
}
/*
* @see com.spotify.folsom.BinaryMemcacheClient#shutdown()
*/
@Override
public void shutdown() {
rawMemcacheClient.shutdown();
}
@Override
public void registerForConnectionChanges(ConnectionChangeListener listener) {
rawMemcacheClient.registerForConnectionChanges(listener);
}
@Override
public void unregisterForConnectionChanges(ConnectionChangeListener listener) {
rawMemcacheClient.unregisterForConnectionChanges(listener);
}
/*
* @see com.spotify.folsom.BinaryMemcacheClient#isConnected()
*/
@Override
public boolean isConnected() {
return rawMemcacheClient.isConnected();
}
@Override
public int numTotalConnections() {
return rawMemcacheClient.numTotalConnections();
}
@Override
public int numActiveConnections() {
return rawMemcacheClient.numActiveConnections();
}
@Override
public String toString() {
return "AsciiMemcacheClient(" + rawMemcacheClient + ")";
}
}