org.infinispan.client.hotrod.impl.operations.HotRodOperation Maven / Gradle / Ivy
The newest version!
package org.infinispan.client.hotrod.impl.operations;
import static org.infinispan.client.hotrod.logging.Log.HOTROD;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.infinispan.client.hotrod.DataFormat;
import org.infinispan.client.hotrod.configuration.Configuration;
import org.infinispan.client.hotrod.exceptions.HotRodClientException;
import org.infinispan.client.hotrod.impl.ClientTopology;
import org.infinispan.client.hotrod.impl.protocol.Codec;
import org.infinispan.client.hotrod.impl.protocol.HeaderParams;
import org.infinispan.client.hotrod.impl.protocol.HotRodConstants;
import org.infinispan.client.hotrod.impl.transport.netty.ByteBufUtil;
import org.infinispan.client.hotrod.impl.transport.netty.ChannelFactory;
import org.infinispan.client.hotrod.impl.transport.netty.HeaderDecoder;
import org.infinispan.client.hotrod.logging.Log;
import org.infinispan.client.hotrod.logging.LogFactory;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.handler.codec.DecoderException;
import net.jcip.annotations.Immutable;
/**
* Generic Hot Rod operation. It is aware of {@link org.infinispan.client.hotrod.Flag}s and it is targeted against a
* cache name. This base class encapsulates the knowledge of writing and reading a header, as described in the
* Hot Rod protocol specification
*
* @author [email protected]
* @since 4.1
*/
@Immutable
public abstract class HotRodOperation extends CompletableFuture implements HotRodConstants, Runnable {
private static final Log log = LogFactory.getLog(HotRodOperation.class);
private static final AtomicLong MSG_ID = new AtomicLong(1);
protected final Codec codec;
protected final Configuration cfg;
protected final ChannelFactory channelFactory;
protected final HeaderParams header;
protected volatile ScheduledFuture> timeoutFuture;
private Channel channel;
private static final byte NO_TX = 0;
private static final byte XA_TX = 1;
protected HotRodOperation(short requestCode, short responseCode, Codec codec, int flags, Configuration cfg,
byte[] cacheName, AtomicReference clientTopology, ChannelFactory channelFactory,
DataFormat dataFormat) {
this.cfg = cfg;
this.codec = codec;
this.channelFactory = channelFactory;
// TODO: we could inline all the header here
this.header = new HeaderParams(requestCode, responseCode, flags, NO_TX, MSG_ID.getAndIncrement(), dataFormat, clientTopology)
.cacheName(cacheName)
.topologyAge(channelFactory.getTopologyAge());
}
protected HotRodOperation(short requestCode, short responseCode, Codec codec, int flags, Configuration cfg, byte[] cacheName, AtomicReference clientTopology, ChannelFactory channelFactory) {
this(requestCode, responseCode, codec, flags, cfg, cacheName, clientTopology, channelFactory, null);
}
public abstract CompletableFuture execute();
public HeaderParams header() {
return header;
}
protected void sendHeaderAndRead(Channel channel) {
scheduleRead(channel);
sendHeader(channel);
}
protected void sendHeader(Channel channel) {
ByteBuf buf = channel.alloc().buffer(codec.estimateHeaderSize(header));
codec.writeHeader(buf, header);
channel.writeAndFlush(buf);
}
protected void scheduleRead(Channel channel) {
channel.pipeline().get(HeaderDecoder.class).registerOperation(channel, this);
}
public void releaseChannel(Channel channel) {
channelFactory.releaseChannel(channel);
}
public void channelInactive(Channel channel) {
SocketAddress address = channel.remoteAddress();
completeExceptionally(log.connectionClosed(address, address));
}
public void exceptionCaught(Channel channel, Throwable cause) {
while (cause instanceof DecoderException && cause.getCause() != null) {
cause = cause.getCause();
}
try {
if (closeChannelForCause(cause)) {
HOTROD.closingChannelAfterError(channel, cause);
channel.close();
}
} finally {
completeExceptionally(cause);
}
}
protected final boolean isServerError(Throwable t) {
return t instanceof HotRodClientException && ((HotRodClientException) t).isServerError();
}
protected final boolean closeChannelForCause(Throwable t) {
// don't close the channel, server just sent an error, there's nothing wrong with the channel
return !(isServerError(t)
// The request just timed out, the channel still ok.
|| t instanceof SocketTimeoutException);
}
protected void sendArrayOperation(Channel channel, byte[] array) {
// 1) write [header][array length][key]
ByteBuf buf = channel.alloc().buffer(codec.estimateHeaderSize(header) + ByteBufUtil.estimateArraySize(array));
codec.writeHeader(buf, header);
ByteBufUtil.writeArray(buf, array);
channel.writeAndFlush(buf);
}
public abstract void acceptResponse(ByteBuf buf, short status, HeaderDecoder decoder);
@Override
public String toString() {
String cn = cacheName() == null || cacheName().length == 0 ? "(default)" : new String(cacheName());
StringBuilder sb = new StringBuilder(64);
sb.append(getClass().getSimpleName()).append('{').append(cn);
addParams(sb);
sb.append(", flags=").append(Integer.toHexString(flags()));
if (channel != null) {
sb.append(", connection=").append(channel.remoteAddress());
}
sb.append('}');
return sb.toString();
}
protected void addParams(StringBuilder sb) {
}
@Override
public boolean complete(T value) {
cancelTimeout();
return super.complete(value);
}
@Override
public boolean completeExceptionally(Throwable ex) {
cancelTimeout();
return super.completeExceptionally(ex);
}
public void scheduleTimeout(Channel channel) {
assert timeoutFuture == null : this + " is trying to override a timeout";
this.channel = channel;
this.timeoutFuture = channel.eventLoop().schedule(this, channelFactory.socketTimeout(), TimeUnit.MILLISECONDS);
}
private void cancelTimeout() {
// Timeout future is not set if the operation completes before scheduling a read:
// see RemoveClientListenerOperation.fetchChannelAndInvoke
if (timeoutFuture != null) {
timeoutFuture.cancel(false);
}
}
@Override
public void run() {
exceptionCaught(channel, new SocketTimeoutException(this + " timed out after " + channelFactory.socketTimeout() + " ms"));
}
public final DataFormat dataFormat() {
return header.dataFormat();
}
protected final byte[] cacheName() {
return header.cacheName();
}
protected final int flags() {
return header.flags();
}
public Codec getCodec() {
return codec;
}
}