package edu.brown.protorpc;
import ca.evanjones.protorpc.Protocol;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.RpcCallback;
import com.google.protobuf.RpcController;
import edu.brown.utils.StringUtil;
public class ProtoRpcController implements RpcController {
// Call reset() to initialize everything
public ProtoRpcController() { reset(); }
// This is effectively the "constructor" for this class.
@Override
public void reset() {
eventLoop = null;
builder = null;
callback = null;
status = Protocol.Status.INVALID;
errorText = null;
}
@Override
public String errorText() {
checkRpcState();
if (status == Protocol.Status.OK) {
throw new IllegalStateException("RPC has not failed");
}
return errorText;
}
@Override
public boolean failed() {
checkRpcState();
return status != Protocol.Status.OK;
}
private void checkRpcState() {
if (status == Protocol.Status.INVALID) {
String message = "No RPC active.";
if (callback != null) {
message = "RPC has not completed.";
}
throw new IllegalStateException(message);
}
}
@Override
public boolean isCanceled() {
throw new UnsupportedOperationException("unimplemented");
}
@Override
public void notifyOnCancel(RpcCallback<Object> callback) {
throw new UnsupportedOperationException("unimplemented");
}
@Override
public void setFailed(String reason) {
throw new UnsupportedOperationException("unimplemented");
}
@Override
public void startCancel() {
throw new UnsupportedOperationException("unimplemented");
}
/** Wait for the current RPC to complete. */
public void block() {
if (status == Protocol.Status.OK) {
// Assume mockFinishRpcForTest was called.
return;
}
assert eventLoop != null;
assert callback != null;
// TODO: If we have a threaded implementation, this will need to be more complicated.
while (callback != null) {
eventLoop.runOnce();
}
}
public void startRpc(EventLoop eventLoop, Message.Builder builder, RpcCallback<Message> callback) {
if (this.callback != null) {
throw new IllegalStateException(
"ProtoRpcController already in use by another RPC call; " +
"wait for callback before reusing.");
}
if (callback == null) {
throw new NullPointerException("callback cannot be null");
}
assert this.eventLoop == null;
assert eventLoop != null;
assert this.builder == null;
assert builder != null;
this.eventLoop = eventLoop;
this.builder = builder;
this.callback = callback;
status = Protocol.Status.INVALID;
}
/** Finish an RPC for unit tests. */
// TODO: Create a factory to avoid needing this?
public void mockFinishRpcForTest() {
assert status == Protocol.Status.INVALID || status == Protocol.Status.OK;
assert callback == null;
status = Protocol.Status.OK;
}
public void finishRpcSuccess(ByteString response) {
assert response != null;
finishRpc(Protocol.Status.OK, response, null);
}
public void finishRpcFailure(Protocol.Status status, String errorText) {
finishRpc(status, null, errorText);
}
private void finishRpc(Protocol.Status status, ByteString response, String errorText) {
assert this.status == Protocol.Status.INVALID :
String.format("Trying to invoke finishRPC more than once [status=%s, errorText=%s]\n%s",
this.status, errorText, response);
assert callback != null;
assert status != Protocol.Status.INVALID;
boolean success = status == Protocol.Status.OK;
if (success) {
assert response != null;
assert errorText == null;
} else {
assert response == null;
assert errorText != null;
}
// Set the status and reset state before we invoke the callback
this.status = status;
this.errorText = errorText;
eventLoop = null;
Message.Builder tempBuilder = builder;
builder = null;
RpcCallback<Message> tempCallback = callback;
callback = null;
Message result = null;
if (success) {
try {
tempBuilder.mergeFrom(response);
result = tempBuilder.build();
} catch (InvalidProtocolBufferException e) {
System.err.println("RESPONSE: " + StringUtil.hexDump(response));
System.err.println("BUILDER: " + tempBuilder.toString());
throw new RuntimeException(e);
}
}
tempCallback.run(result);
}
private EventLoop eventLoop;
private Message.Builder builder;
private RpcCallback<Message> callback;
private Protocol.Status status;
private String errorText;
}