package org.infinispan.server.hotrod;
import static org.infinispan.server.hotrod.transport.ExtendedByteBuf.readMaybeByte;
import static org.infinispan.server.hotrod.transport.ExtendedByteBuf.readMaybeLong;
import static org.infinispan.server.hotrod.transport.ExtendedByteBuf.readMaybeRangedBytes;
import static org.infinispan.server.hotrod.transport.ExtendedByteBuf.readMaybeString;
import static org.infinispan.server.hotrod.transport.ExtendedByteBuf.readMaybeVInt;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.infinispan.AdvancedCache;
import org.infinispan.commons.logging.LogFactory;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.container.versioning.NumericVersion;
import org.infinispan.context.Flag;
import org.infinispan.server.core.ServerConstants;
import org.infinispan.server.core.transport.NettyTransport;
import org.infinispan.server.hotrod.logging.Log;
import org.infinispan.stats.Stats;
import org.infinispan.util.concurrent.TimeoutException;
import io.netty.buffer.ByteBuf;
;
/**
* HotRod protocol decoder specific for specification version 1.0.
*
* @author Galder ZamarreƱo
* @since 4.1
*/
public class Decoder10 implements VersionedDecoder {
private final static Log log = LogFactory.getLog(Decoder10.class, Log.class);
private final static boolean isTrace = log.isTraceEnabled();
@Override
public boolean readHeader(ByteBuf buffer, byte version, long messageId, HotRodHeader header) throws Exception {
if (header.op == null) {
Optional<Byte> maybeByte = readMaybeByte(buffer);
if (!maybeByte.flatMap(streamOp -> readMaybeString(buffer).map(cacheName -> {
header.op = HotRodOperation.fromRequestOpCode(streamOp);
if (isTrace) log.tracef("Operation code: %d has been matched to %s", streamOp, header.op);
header.cacheName = cacheName;
buffer.markReaderIndex();
return streamOp;
})).isPresent()) {
return false;
} else if (header.op == null) {
throw new HotRodUnknownOperationException("Unknown operation: " + maybeByte.get(), version, messageId);
}
}
return readMaybeVInt(buffer).flatMap(flag -> readMaybeByte(buffer).flatMap(clientIntelligence ->
readMaybeVInt(buffer).flatMap(topologyId -> readMaybeByte(buffer).map(txId -> {
if (txId != 0) {
throw new UnsupportedOperationException("Transaction types other than 0 (NO_TX) is not supported " +
"at this stage. Saw TX_ID of " + txId);
}
header.flag = flag;
header.clientIntel = clientIntelligence;
header.topologyId = topologyId;
buffer.markReaderIndex();
return txId;
})))).isPresent();
}
@Override
public CacheDecodeContext.RequestParameters readParameters(HotRodHeader header, ByteBuf buffer) {
switch (header.op) {
case REMOVE_IF_UNMODIFIED:
return readMaybeLong(buffer).map(l -> new CacheDecodeContext.RequestParameters(-1,
new CacheDecodeContext.ExpirationParam(-1, TimeUnitValue.SECONDS),
new CacheDecodeContext.ExpirationParam(-1, TimeUnitValue.SECONDS), l)
).orElse(null);
case REPLACE_IF_UNMODIFIED:
return readLifespanOrMaxIdle(buffer, hasFlag(header, ProtocolFlag.DefaultLifespan)).flatMap(lifespan ->
readLifespanOrMaxIdle(buffer, hasFlag(header, ProtocolFlag.DefaultMaxIdle)).flatMap(maxIdle ->
readMaybeLong(buffer).flatMap(version -> readMaybeVInt(buffer).map(valueLength ->
new CacheDecodeContext.RequestParameters(valueLength,
new CacheDecodeContext.ExpirationParam(lifespan, TimeUnitValue.SECONDS),
new CacheDecodeContext.ExpirationParam(maxIdle, TimeUnitValue.SECONDS), version)
))
)
).orElse(null);
default:
return readLifespanOrMaxIdle(buffer, hasFlag(header, ProtocolFlag.DefaultLifespan)).flatMap(lifespan ->
readLifespanOrMaxIdle(buffer, hasFlag(header, ProtocolFlag.DefaultMaxIdle)).flatMap(maxIdle ->
readMaybeVInt(buffer).map(valueLength ->
new CacheDecodeContext.RequestParameters(valueLength,
new CacheDecodeContext.ExpirationParam(lifespan, TimeUnitValue.SECONDS),
new CacheDecodeContext.ExpirationParam(maxIdle, TimeUnitValue.SECONDS), -1)
)
)
).orElse(null);
}
}
private boolean hasFlag(HotRodHeader h, ProtocolFlag f) {
return (h.flag & f.getValue()) == f.getValue();
}
private Optional<Integer> readLifespanOrMaxIdle(ByteBuf buffer, boolean useDefault) {
return readMaybeVInt(buffer).map(stream -> {
if (stream <= 0) {
if (useDefault)
return ServerConstants.EXPIRATION_DEFAULT;
else
return ServerConstants.EXPIRATION_NONE;
} else return stream;
});
}
@Override
public Response createSuccessResponse(HotRodHeader header, byte[] prev) {
return createResponse(header, OperationStatus.Success, prev);
}
@Override
public Response createNotExecutedResponse(HotRodHeader header, byte[] prev) {
return createResponse(header, OperationStatus.OperationNotExecuted, prev);
}
@Override
public Response createNotExistResponse(HotRodHeader header) {
return createResponse(header, OperationStatus.KeyDoesNotExist, null);
}
private Response createResponse(HotRodHeader h, OperationStatus st, byte[] prev) {
if (hasFlag(h, ProtocolFlag.ForceReturnPreviousValue))
return new ResponseWithPrevious(h.version, h.messageId, h.cacheName,
h.clientIntel, h.op, st, h.topologyId, Optional.ofNullable(prev));
else
return new EmptyResponse(h.version, h.messageId, h.cacheName, h.clientIntel, h.op, st, h.topologyId);
}
@Override
public Response createGetResponse(HotRodHeader h, CacheEntry<byte[], byte[]> entry) {
HotRodOperation op = h.op;
if (entry != null && op == HotRodOperation.GET)
return new GetResponse(h.version, h.messageId, h.cacheName, h.clientIntel,
op, OperationStatus.Success, h.topologyId,
entry.getValue());
else if (entry != null && op == HotRodOperation.GET_WITH_VERSION) {
long version = ((NumericVersion) entry.getMetadata().version()).getVersion();
return new GetWithVersionResponse(h.version, h.messageId, h.cacheName,
h.clientIntel, op, OperationStatus.Success, h.topologyId,
entry.getValue(), version);
} else if (op == HotRodOperation.GET)
return new GetResponse(h.version, h.messageId, h.cacheName, h.clientIntel,
op, OperationStatus.KeyDoesNotExist, h.topologyId, null);
else
return new GetWithVersionResponse(h.version, h.messageId, h.cacheName,
h.clientIntel, op, OperationStatus.KeyDoesNotExist,
h.topologyId, null, 0);
}
@Override
public void customReadHeader(HotRodHeader header, ByteBuf buffer, CacheDecodeContext hrCtx, List<Object> out) {
// Do nothing
}
@Override
public void customReadKey(HotRodHeader h, ByteBuf buffer, CacheDecodeContext hrCtx, List<Object> out) {
switch (h.op) {
case BULK_GET:
case BULK_GET_KEYS:
Optional<Integer> number = readMaybeVInt(buffer);
number.ifPresent(n -> {
hrCtx.operationDecodeContext = n;
buffer.markReaderIndex();
out.add(hrCtx);
});
break;
case QUERY:
Optional<byte[]> bytes = readMaybeRangedBytes(buffer);
bytes.ifPresent(b -> {
hrCtx.operationDecodeContext = b;
buffer.markReaderIndex();
out.add(hrCtx);
});
break;
}
}
@Override
public void customReadValue(HotRodHeader header, ByteBuf buffer, CacheDecodeContext hrCtx, List<Object> out) {
// Do nothing
}
@Override
public StatsResponse createStatsResponse(CacheDecodeContext hrCtx, NettyTransport t) {
HotRodHeader h = hrCtx.header;
Stats cacheStats = hrCtx.cache.getStats();
Map<String, String> stats = new HashMap<>();
stats.put("timeSinceStart", String.valueOf(cacheStats.getTimeSinceStart()));
stats.put("currentNumberOfEntries", String.valueOf(cacheStats.getCurrentNumberOfEntries()));
stats.put("totalNumberOfEntries", String.valueOf(cacheStats.getTotalNumberOfEntries()));
stats.put("stores", String.valueOf(cacheStats.getStores()));
stats.put("retrievals", String.valueOf(cacheStats.getRetrievals()));
stats.put("hits", String.valueOf(cacheStats.getHits()));
stats.put("misses", String.valueOf(cacheStats.getMisses()));
stats.put("removeHits", String.valueOf(cacheStats.getRemoveHits()));
stats.put("removeMisses", String.valueOf(cacheStats.getRemoveMisses()));
stats.put("totalBytesRead", t.getTotalBytesRead());
stats.put("totalBytesWritten", t.getTotalBytesWritten());
return new StatsResponse(h.version, h.messageId, h.cacheName, h.clientIntel, stats, h.topologyId);
}
@Override
public ErrorResponse createErrorResponse(HotRodHeader header, Throwable t) {
if (t instanceof IOException) {
return new ErrorResponse(header.version, header.messageId, header.cacheName, header.clientIntel,
OperationStatus.ParseError, header.topologyId, t.toString());
} else if (t instanceof TimeoutException) {
return new ErrorResponse(header.version, header.messageId, header.cacheName, header.clientIntel,
OperationStatus.OperationTimedOut, header.topologyId, t.toString());
} else {
return new ErrorResponse(header.version, header.messageId, header.cacheName, header.clientIntel,
OperationStatus.ServerError, header.topologyId, t.toString());
}
}
@Override
public AdvancedCache<byte[], byte[]> getOptimizedCache(HotRodHeader h, AdvancedCache<byte[], byte[]> c, Configuration cacheCfg) {
if (!hasFlag(h, ProtocolFlag.ForceReturnPreviousValue)) {
switch (h.op) {
case PUT:
case PUT_IF_ABSENT:
return c.withFlags(Flag.IGNORE_RETURN_VALUES);
}
}
return c;
}
}