// Copyright (c) 2010 Shardul Deo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package com.googlecode.protobuf.socketrpc;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.google.protobuf.BlockingRpcChannel;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.RpcCallback;
import com.google.protobuf.RpcChannel;
import com.google.protobuf.RpcController;
import com.google.protobuf.ServiceException;
import com.google.protobuf.Descriptors.MethodDescriptor;
import com.googlecode.protobuf.socketrpc.RpcConnectionFactory.Connection;
import com.googlecode.protobuf.socketrpc.SocketRpcProtos.ErrorReason;
import com.googlecode.protobuf.socketrpc.SocketRpcProtos.Response;
/**
* {@link RpcChannel} implementation that uses a {@link RpcConnectionFactory} to
* perform blocking and non-blocking rpcs.
*
* @author Shardul Deo
*/
class RpcChannelImpl implements RpcChannel, BlockingRpcChannel {
private final static Logger LOG =
Logger.getLogger(RpcChannelImpl.class.getName());
private final RpcConnectionFactory connectionFactory;
private final Executor executor;
RpcChannelImpl(RpcConnectionFactory connectionFactory, Executor executor) {
this.connectionFactory = connectionFactory;
this.executor = executor;
}
@Override
public void callMethod(MethodDescriptor method, RpcController controller,
Message request, final Message responsePrototype,
final RpcCallback<Message> done) {
// Must pass in a SocketRpcController
final SocketRpcController socketController
= (SocketRpcController) controller;
// Send request over connection
final Connection connection;
try {
connection = createConnection(socketController);
} catch (ServiceException e) {
// Call done with null, controller has the error information
callbackWithNull(done);
return;
}
try {
sendRpcRequest(method, socketController, request, connection);
} catch (ServiceException e) {
// Call done with null, controller has the error information
try {
callbackWithNull(done);
} finally {
close(connection);
}
return;
}
// Listen for the response using the executor
executor.execute(new Runnable() {
@Override
public void run() {
try {
// Thread blocks here until server sends a response
Response rpcResponse = receiveRpcResponse(socketController,
connection);
Message response = handleRpcResponse(responsePrototype, rpcResponse,
socketController);
// Callback if failed or server invoked callback
if (socketController.failed() || rpcResponse.getCallback()) {
if (done != null) {
done.run(response);
}
}
} catch (ServiceException e) {
// Call done with null, controller has the error information
callbackWithNull(done);
} finally {
close(connection);
}
}
});
}
private static void callbackWithNull(RpcCallback<Message> done) {
if (done != null) {
done.run(null);
}
}
@Override
public Message callBlockingMethod(MethodDescriptor method,
RpcController controller, Message request, Message responsePrototype)
throws ServiceException {
// Must pass in a SocketRpcController
SocketRpcController socketController = (SocketRpcController) controller;
final Connection connection = createConnection(socketController);
try {
sendRpcRequest(method, socketController, request, connection);
Response rpcResponse = receiveRpcResponse(socketController, connection);
return handleRpcResponse(responsePrototype, rpcResponse,
socketController);
} finally {
close(connection);
}
}
private Connection createConnection(SocketRpcController socketController)
throws ServiceException {
try {
return connectionFactory.createConnection();
} catch (UnknownHostException e) {
return handleError(socketController, ErrorReason.UNKNOWN_HOST,
"Could not find host: " + e.getMessage(), e);
} catch (IOException e) {
return handleError(socketController, ErrorReason.IO_ERROR, String.format(
"Error creating connection using factory %s", connectionFactory), e);
}
}
private void close(Connection connection) {
try {
connection.close();
} catch (IOException e) {
// It's ok
}
}
private void sendRpcRequest(MethodDescriptor method,
SocketRpcController socketController, Message request,
Connection connection) throws ServiceException {
// Check request
if (!request.isInitialized()) {
handleError(socketController, ErrorReason.INVALID_REQUEST_PROTO,
"Request is uninitialized", null);
}
// Create RPC request protobuf
SocketRpcProtos.Request rpcRequest = SocketRpcProtos.Request.newBuilder()
.setRequestProto(request.toByteString())
.setServiceName(method.getService().getFullName())
.setMethodName(method.getName())
.build();
// Send request
try {
connection.sendProtoMessage(rpcRequest);
} catch (IOException e) {
handleError(socketController, ErrorReason.IO_ERROR, String.format(
"Error writing over connection %s", connection), e);
}
}
private Response receiveRpcResponse(SocketRpcController socketController,
Connection connection) throws ServiceException {
try {
// Read and handle response
SocketRpcProtos.Response.Builder builder = SocketRpcProtos.Response
.newBuilder();
connection.receiveProtoMessage(builder);
if (!builder.isInitialized()) {
return handleError(socketController, ErrorReason.BAD_RESPONSE_PROTO,
"Bad response from server", null);
}
return builder.build();
} catch (IOException e) {
return handleError(socketController, ErrorReason.IO_ERROR, String.format(
"Error reading over connection %s", connection), e);
}
}
private Message handleRpcResponse(Message responsePrototype,
SocketRpcProtos.Response rpcResponse,
SocketRpcController socketController)
throws ServiceException {
// Check for error
if (rpcResponse.hasError()) {
return handleError(socketController, rpcResponse.getErrorReason(),
rpcResponse.getError(), null);
}
if (!rpcResponse.hasResponseProto()) {
// No response
return null;
}
try {
Message.Builder builder = responsePrototype.newBuilderForType()
.mergeFrom(rpcResponse.getResponseProto());
if (!builder.isInitialized()) {
return handleError(socketController, ErrorReason.BAD_RESPONSE_PROTO,
"Uninitialized RPC Response Proto", null);
}
return builder.build();
} catch (InvalidProtocolBufferException e) {
return handleError(socketController, ErrorReason.BAD_RESPONSE_PROTO,
"Response could be parsed as "
+ responsePrototype.getClass().getName(), e);
}
}
private <T> T handleError(SocketRpcController socketController,
ErrorReason reason, String msg, Exception e)
throws ServiceException {
if (e == null) {
LOG.log(Level.WARNING, reason + ": " + msg);
} else {
LOG.log(Level.WARNING, reason + ": " + msg, e);
}
socketController.setFailed(msg, reason);
throw new ServiceException(msg);
}
}