code: public RpcResponse call(RpcRequest req) {
          for (Filter filter : filters) {
              RpcRequest tmp = filter.alterRequest(req);
              if (tmp != null) {
                  req = tmp;
              }
          }
  
          RpcResponse resp = null;
  
          for (Filter filter : filters) {
              resp = filter.preInvoke(req);
              if (resp != null) {
                  break;
              }
          }
  
          if (resp == null) {
              resp = callInternal(req);
          }
  
          for (int i = filters.size() - 1; i >= 0; i--) {
              RpcResponse tmp = filters.get(i).postInvoke(req, resp);
              if (tmp != null) {
                  resp = tmp;
              }
          }
  
          return resp;
      }
docs: Calls the method associated with the RpcRequest and wraps the result as a RpcResponse.
  
  Filters are executed in 3 batches.
  
  * For each filter (in order registered): filter.alterRequest()
  * For each filter (in order registered): filter.preInvoke().  If any filter returns a non-null RpcResponse,
  the loop is terminated and invoke on the handler skipped.  postInvoke() loop below still runs.
  * If no filter returns a non-null RpcResponse, then the handler is invoked based on the method in the RpcRequest
  (this is the common case)
  * For each filter (reverse order registered): filter.postInvoke() is executed.  All filters are executed in this
  loop.
  * Last RpcResponse is returned
  
  @param req Request to process
  @return RpcResponse that pairs with this request.  May contain a result or an error