package edu.uw.zookeeper.protocol.client;
import io.netty.buffer.ByteBuf;
import java.io.IOException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.logging.log4j.LogManager;
import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
import com.google.common.base.Optional;
import edu.uw.zookeeper.common.Automaton;
import edu.uw.zookeeper.common.Automatons;
import edu.uw.zookeeper.common.Pair;
import edu.uw.zookeeper.common.Reference;
import edu.uw.zookeeper.common.Stateful;
import edu.uw.zookeeper.common.Automatons.AutomatonListener;
import edu.uw.zookeeper.net.Decoder;
import edu.uw.zookeeper.net.Encoder;
import edu.uw.zookeeper.protocol.ConnectMessage;
import edu.uw.zookeeper.protocol.Frame;
import edu.uw.zookeeper.protocol.Message;
import edu.uw.zookeeper.protocol.Operation;
import edu.uw.zookeeper.protocol.ProtocolCodec;
import edu.uw.zookeeper.protocol.ProtocolMessageAutomaton;
import edu.uw.zookeeper.protocol.ProtocolState;
import edu.uw.zookeeper.protocol.ProtocolResponseMessage;
import edu.uw.zookeeper.protocol.proto.OpCode;
import edu.uw.zookeeper.protocol.proto.OpCodeXid;
/**
* Implemented for the case where encode is called by a different thread than decode,
* but it is not safe for multiple threads to call encode or multiple
* threads to call decode.
*/
public class ClientProtocolCodec
implements ProtocolCodec<Message.ClientSession, Message.ServerSession, Message.ClientSession, Message.ServerSession> {
public static ClientProtocolCodec defaults() {
return newInstance(ProtocolState.ANONYMOUS);
}
public static ClientProtocolCodec newInstance(ProtocolState state) {
Automatons.SynchronizedEventfulAutomaton<ProtocolState, Object,?> automaton =
Automatons.createSynchronizedEventful(
Automatons.createEventful(
Automatons.createLogging(
LogManager.getLogger(ClientProtocolCodec.class),
ProtocolMessageAutomaton.asAutomaton(state))));
Pending pending = Pending.newInstance();
Encoder<? super Message.ClientSession, ?> encoder =
Frame.FramedEncoder.create(
ClientProtocolEncoder.newInstance(automaton));
Decoder<Optional<Message.ServerSession>, ?> decoder =
Frame.FramedDecoder.create(
Frame.FrameDecoder.getDefault(),
ClientProtocolDecoder.newInstance(automaton, pending));
return new ClientProtocolCodec(automaton, encoder, decoder, pending.get());
}
protected static class Pending implements Function<Integer, OpCode>, Reference<Queue<Pair<Integer, OpCode>>> {
public static Pending newInstance() {
return new Pending(new ConcurrentLinkedQueue<Pair<Integer, OpCode>>());
}
// must be thread-safe
private final Queue<Pair<Integer, OpCode>> queue;
public Pending(Queue<Pair<Integer, OpCode>> queue) {
this.queue = queue;
}
@Override
public OpCode apply(Integer xid) {
Pair<Integer, OpCode> next = queue.peek();
if (next == null) {
throw new IllegalStateException(String.format("Unexpected xid (%d), no pending requests", xid));
}
if (xid.equals(next.first())) {
return next.second();
} else {
throw new IllegalArgumentException(String.format("Unexpected xid (%d), expecting %s", xid, next));
}
}
@Override
public Queue<Pair<Integer, OpCode>> get() {
return queue;
}
}
protected final Automatons.EventfulAutomaton<ProtocolState, Object> automaton;
protected final Encoder<? super Message.ClientSession, ?> encoder;
protected final Decoder<Optional<Message.ServerSession>, ?> decoder;
protected final Queue<Pair<Integer, OpCode>> pending;
protected ClientProtocolCodec(
Automatons.EventfulAutomaton<ProtocolState, Object> automaton,
Encoder<? super Message.ClientSession, ?> encoder,
Decoder<Optional<Message.ServerSession>, ?> decoder,
Queue<Pair<Integer, OpCode>> pending) {
this.automaton = automaton;
this.encoder = encoder;
this.decoder = decoder;
this.pending = pending;
}
@Override
public void subscribe(AutomatonListener<ProtocolState> listener) {
automaton.subscribe(listener);
}
@Override
public boolean unsubscribe(AutomatonListener<ProtocolState> listener) {
return automaton.unsubscribe(listener);
}
@Override
public Class<Message.ClientSession> encodeType() {
return Message.ClientSession.class;
}
@Override
public Class<? extends Message.ServerSession> decodeType() {
return Message.ServerSession.class;
}
/**
* Don't call concurrently!
*/
@Override
public void encode(Message.ClientSession input, ByteBuf output) throws IOException {
encoder.encode(input, output);
automaton.apply(input);
// we only need to remember xid -> opcode of pending messages
if (input instanceof Operation.RequestId) {
int xid = ((Operation.RequestId) input).xid();
if (! OpCodeXid.has(xid)) {
assert (input instanceof Operation.ProtocolRequest);
OpCode opcode = ((Operation.ProtocolRequest<?>) input).record().opcode();
Pair<Integer, OpCode> pair = Pair.create(xid, opcode);
pending.add(pair);
}
}
}
/**
* Don't call concurrently!
*/
@Override
public Optional<Message.ServerSession> decode(ByteBuf input)
throws IOException {
Optional<Message.ServerSession> out = decoder.decode(input);
if (out.isPresent()) {
automaton.apply(out.get());
Message.ServerSession reply = out.get();
// the peek and poll need to be atomic
Pair<Integer, OpCode> next = pending.peek();
if ((next != null) && (reply instanceof Operation.RequestId)) {
if (next.first().equals(((Operation.RequestId) reply).xid())) {
pending.poll();
}
}
}
return out;
}
@Override
public ProtocolState state() {
return automaton.state();
}
@Override
public Optional<Automaton.Transition<ProtocolState>> apply(
ProtocolState input) {
return automaton.apply(input);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).add("state", state()).toString();
}
public static class ClientProtocolEncoder implements
Stateful<ProtocolState>,
Encoder<Message.ClientSession, Message.ClientSession> {
public static ClientProtocolEncoder newInstance(
Stateful<ProtocolState> stateful) {
return new ClientProtocolEncoder(stateful);
}
private final Stateful<ProtocolState> stateful;
private ClientProtocolEncoder(
Stateful<ProtocolState> stateful) {
this.stateful = stateful;
}
@Override
public ProtocolState state() {
return stateful.state();
}
@Override
public Class<Message.ClientSession> encodeType() {
return Message.ClientSession.class;
}
@Override
public void encode(Message.ClientSession input, ByteBuf output) throws IOException {
ProtocolState state = state();
switch (state) {
case ANONYMOUS:
case CONNECTING:
case CONNECTED:
case DISCONNECTING:
input.encode(output);
break;
default:
throw new IllegalStateException(state.toString());
}
}
}
public static class ClientProtocolDecoder implements
Stateful<ProtocolState>,
Decoder<Message.ServerSession, Message.ServerSession> {
public static ClientProtocolDecoder newInstance(
Stateful<ProtocolState> stateful,
Function<Integer, OpCode> xidToOpCode) {
return new ClientProtocolDecoder(stateful, xidToOpCode);
}
private final Stateful<ProtocolState> stateful;
private final Function<Integer, OpCode> xidToOpCode;
private ClientProtocolDecoder(
Stateful<ProtocolState> stateful,
Function<Integer, OpCode> xidToOpCode) {
this.stateful = stateful;
this.xidToOpCode = xidToOpCode;
}
@Override
public ProtocolState state() {
return stateful.state();
}
@Override
public Class<? extends Message.ServerSession> decodeType() {
return Message.ServerSession.class;
}
@Override
public Message.ServerSession decode(ByteBuf input) throws IOException {
ProtocolState state = state();
Message.ServerSession out;
switch (state) {
case CONNECTING:
out = ConnectMessage.Response.decode(input);
break;
case CONNECTED:
case DISCONNECTING:
out = ProtocolResponseMessage.decode(xidToOpCode, input);
break;
default:
throw new IllegalStateException(state.toString());
}
return out;
}
}
}