/** Main dispatch method called from the LatherBoss. */
  public LatherValue dispatch(LatherContext ctx, String method, LatherValue arg)
      throws LatherRemoteException {

    Integer agentId = null;
    if (!haService.alertTriggersHaveInitialized()) {
      if (log.isDebugEnabled()) {
        log.debug("Not ready - received request for " + method + " from " + ctx.getCallerIP());
      }
      throw new LatherRemoteException("Server still initializing");
    }

    if (log.isDebugEnabled()) {
      log.debug("Request for " + method + "() from " + ctx.getCallerIP());
    }

    if (!HAUtil.isMasterNode()) {
      log.warn("Non-primary server received communication from an agent.  Request will be denied.");
      throw new LatherRemoteException(
          "This server is not the primary node in the HA configuration. Agent request denied.");
    }

    if (secureCommands.contains(method)) {
      if (!(arg instanceof SecureAgentLatherValue)) {
        log.warn(
            "Authenticated call made from "
                + ctx.getCallerIP()
                + " which did not subclass the correct authentication class");
        throw new LatherRemoteException("Unauthorized agent denied");
      }

      String agentToken = ((SecureAgentLatherValue) arg).getAgentToken();
      validateAgent(ctx, agentToken);
      synchronized (tokensToTime) {
        tokensToTime.put(agentToken, System.currentTimeMillis());
      }
      try {
        Agent a = agentManager.getAgent(agentToken);
        agentId = a.getId();
      } catch (AgentNotFoundException e) {
        log.debug(e, e);
      }
    }

    AgentConnection conn = null;
    long start = 0;
    try {
      conn = agentManager.getAgentConnection(method, ctx.getCallerIP(), agentId);
      start = now();
      return runCommand(ctx, method, arg);
    } catch (LatherRemoteException e) {
      concurrentStatsCollector.addStat(1, LATHER_REMOTE_EXCEPTION);
      throw e;
    } finally {
      if (conn != null) {
        agentManager.disconnectAgent(conn);
      }
      long duration = now() - start;
      concurrentStatsCollector.addStat(duration, LATHER_RUN_COMMAND_TIME);
    }
  }