/**
   * Sends an RPC request to the given {@link InetSocketAddress}.
   *
   * @param remoteSocketAddress the remote address to send the request to
   * @param request the RPC request to send
   * @return the return value of the RPC call, possibly <code>null</code>
   * @throws Throwable any exception that is thrown by the remote receiver of the RPC call
   */
  Object sendRPCRequest(final InetSocketAddress remoteSocketAddress, final RPCRequest request)
      throws Throwable {

    if (this.shutdownRequested.get())
      throw new IOException("Shutdown of RPC service has already been requested");

    final long start = System.currentTimeMillis();
    final DatagramPacket[] packets = this.messageToPackets(remoteSocketAddress, request);
    final Integer messageID = Integer.valueOf(request.getMessageID());

    final RPCRequestMonitor requestMonitor = new RPCRequestMonitor();

    this.pendingRequests.put(messageID, requestMonitor);

    RPCResponse rpcResponse = null;
    int numberOfRetries;
    try {

      numberOfRetries = this.networkThread.send(packets);

      // Wait for the response
      synchronized (requestMonitor) {
        while (true) {

          if (requestMonitor.rpcResponse != null) {
            rpcResponse = requestMonitor.rpcResponse;
            break;
          }

          final long sleepTime = RPC_TIMEOUT - (System.currentTimeMillis() - start);
          if (sleepTime > 0L) requestMonitor.wait(sleepTime);
          else break;
        }
      }

    } finally {
      // Request is no longer pending
      this.pendingRequests.remove(messageID);
    }

    if (rpcResponse == null)
      throw new IOException(
          "Unable to complete RPC of method "
              + request.getMethodName()
              + " on "
              + remoteSocketAddress);

    // Report the successful call to the statistics module
    final String methodName = request.getMethodName();
    this.statistics.reportSuccessfulTransmission(methodName, packets.length, numberOfRetries);
    this.statistics.reportRTT(methodName, (int) (System.currentTimeMillis() - start));

    // TODO: Send clean up message

    if (rpcResponse instanceof RPCReturnValue) return ((RPCReturnValue) rpcResponse).getRetVal();
    throw ((RPCThrowable) rpcResponse).getThrowable();
  }
  /**
   * Transforms the given {@link Throwable} into a string and wraps it into an {@link IOException}.
   *
   * @param request the RPC request which caused the {@link Throwable} to be wrapped
   * @param throwable the {@link Throwable} to be wrapped
   * @return the {@link} IOException created from the {@link Throwable}
   */
  private static IOException wrapInIOException(
      final RPCRequest request, final Throwable throwable) {

    final StringBuilder sb = new StringBuilder("The remote procedure call of method ");
    sb.append(request.getInterfaceName());
    sb.append('.');
    sb.append(request.getMethodName());
    sb.append(" caused an unregistered exception: ");
    sb.append(StringUtils.stringifyException(throwable));

    return new IOException(sb.toString());
  }
  private void processIncomingRPCRequest(
      final InetSocketAddress remoteSocketAddress, final RPCRequest rpcRequest) {

    final Integer messageID = Integer.valueOf(rpcRequest.getMessageID());

    if (this.requestsBeingProcessed.putIfAbsent(messageID, rpcRequest) != null) {
      Log.debug(
          "Request " + rpcRequest.getMessageID() + " is already being processed at the moment");
      return;
    }

    final CachedResponse cachedResponse = this.cachedResponses.get(messageID);
    if (cachedResponse != null) {
      try {
        final int numberOfRetries = this.networkThread.send(cachedResponse.packets);
        this.statistics.reportSuccessfulTransmission(
            rpcRequest.getMethodName() + " (Response)",
            cachedResponse.packets.length,
            numberOfRetries);
      } catch (final Exception e) {
        Log.error("Caught exception while trying to send RPC response: ", e);
      } finally {
        this.requestsBeingProcessed.remove(messageID);
      }
      return;
    }

    final RPCProtocol callbackHandler = this.callbackHandlers.get(rpcRequest.getInterfaceName());
    if (callbackHandler == null) {
      Log.error("Cannot find callback handler for protocol " + rpcRequest.getInterfaceName());
      this.requestsBeingProcessed.remove(messageID);
      return;
    }

    try {
      final Method method =
          callbackHandler
              .getClass()
              .getMethod(rpcRequest.getMethodName(), rpcRequest.getParameterTypes());

      RPCResponse rpcResponse = null;
      try {
        final Object retVal = method.invoke(callbackHandler, rpcRequest.getArgs());
        rpcResponse = new RPCReturnValue(rpcRequest.getMessageID(), retVal);
      } catch (final InvocationTargetException ite) {

        Throwable targetException = ite.getTargetException();

        // Make sure the stack trace is correctly filled
        targetException.getStackTrace();

        if (!this.isThrowableRegistered(targetException.getClass()))
          targetException = wrapInIOException(rpcRequest, targetException);

        rpcResponse = new RPCThrowable(rpcRequest.getMessageID(), targetException);
      }
      final DatagramPacket[] packets = this.messageToPackets(remoteSocketAddress, rpcResponse);
      this.cachedResponses.put(messageID, new CachedResponse(System.currentTimeMillis(), packets));

      final int numberOfRetries = this.networkThread.send(packets);
      this.statistics.reportSuccessfulTransmission(
          rpcRequest.getMethodName() + " (Response)", packets.length, numberOfRetries);

    } catch (final Exception e) {
      Log.error("Caught processing RPC request: ", e);
    } finally {
      this.requestsBeingProcessed.remove(messageID);
    }
  }
 public static RPCRequest createRequest() {
   int xid = XID.next();
   RPCRequest base = new RPCRequest(xid, RPC_VERSION);
   base.setProgram(NFS_PROG);
   base.setProcedure(NFS_PROC_COMPOUND);
   base.setProgramVersion(NFS_VERSION);
   assertEquals(xid, base.getXid());
   assertEquals(RPC_MESSAGE_TYPE_CALL, base.getMessageType());
   assertEquals(RPC_VERSION, base.getRpcVersion());
   assertEquals(NFS_PROG, base.getProgram());
   assertEquals(NFS_VERSION, base.getProgramVersion());
   assertEquals(NFS_PROC_COMPOUND, base.getProcedure());
   base.setCredentials(new CredentialsNone());
   base.setVerifier(new VerifierNone());
   assertEquals(RPC_AUTH_NULL, base.getCredentials().getFlavor());
   return base;
 }