/** 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);
    }
  }
  /**
   * Update an Agent's setup information. When an agent's 'setup' is called after it has already
   * been setup, the connection information (i.e. address to contact, port, etc.) are updated but
   * the same agent token is kept.
   */
  private UpdateAgent_result cmdUpdateAgent(LatherContext ctx, UpdateAgent_args args)
      throws LatherRemoteException {
    Agent agent;
    String errRes;

    try {
      checkUserCanManageAgent(ctx, args.getUser(), args.getPword(), "update");
    } catch (PermissionException exc) {
      return new UpdateAgent_result("Permission denied");
    }

    validateAgent(ctx, args.getAgentToken(), false);

    String agentIP = args.getAgentIP();
    int port = args.getAgentPort();
    boolean isNewTransportAgent = args.isNewTransportAgent();
    boolean unidirectional = args.isUnidirectional();
    boolean acceptValidation = args.isAcceptCertificates();

    try {
      agent = agentManager.getAgent(args.getAgentToken());

      if ((errRes =
              testAgentConn(
                  agentIP,
                  port,
                  agent.getAuthToken(),
                  isNewTransportAgent,
                  unidirectional,
                  acceptValidation))
          != null) {
        return new UpdateAgent_result(errRes);
      }

      log.info(
          "Updating agent at "
              + agentIP
              + ":"
              + port
              + ""
              + "; new transport="
              + isNewTransportAgent
              + "; unidirectional="
              + unidirectional);

      if (isNewTransportAgent) {
        agentManager.updateNewTransportAgent(args.getAgentToken(), agentIP, port, unidirectional);
      } else {
        agentManager.updateLegacyAgent(args.getAgentToken(), agentIP, port);
      }

    } catch (AgentNotFoundException exc) {
      return new UpdateAgent_result("Agent not found for update");
    }
    return new UpdateAgent_result();
  }
  /**
   * Registers the agent according to the provided RegisterAgent_args under a new transaction
   *
   * @param args - the RegisterAgent_args
   * @param ids - if the is an existing agent, this collection will contain the ids of the platforms
   *     monitored by this agent after this method execution.
   * @return - RegisterAgent_result
   */
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  private RegisterAgent_result registerAgent(RegisterAgent_args args, Collection<Integer> ids) {
    String agentIP = args.getAgentIP();
    int port = args.getAgentPort();
    String version = args.getVersion();
    boolean isNewTransportAgent = args.isNewTransportAgent();
    boolean unidirectional = args.isUnidirectional();
    boolean acceptValidation = args.isAcceptCertificates();
    String errRes =
        testAgentConn(
            agentIP,
            port,
            args.getAuthToken(),
            isNewTransportAgent,
            unidirectional,
            acceptValidation);

    if (errRes != null) {
      return new RegisterAgent_result(errRes);
    }

    boolean isOldAgentToken = true;
    String agentToken = args.getAgentToken();

    if (agentToken == null) {
      // Generate a unique agent token
      agentToken = SecurityUtil.generateRandomToken();

      while (!agentManager.isAgentTokenUnique(agentToken)) {
        agentToken = SecurityUtil.generateRandomToken();
      }

      isOldAgentToken = false;
    }

    // Check the to see if the agent already exists.
    // Lookup the agent by agent token (if it exists). Otherwise, use the
    // agent IP and port.
    try {
      Agent origAgent;

      if (isOldAgentToken) {
        origAgent = agentManager.getAgent(agentToken);
      } else {
        origAgent = agentManager.getAgent(agentIP, port);
      }

      try {
        ids.addAll(
            platformManager.getPlatformPksByAgentToken(
                authzSubjectManager.getOverlordPojo(), origAgent.getAgentToken()));
      } catch (Exception e) {
        // No platforms found, no a big deal
      }

      log.info(
          "Found preexisting agent during agent registration. "
              + "Updating agent information for "
              + agentIP
              + ":"
              + port
              + "; new transport="
              + isNewTransportAgent
              + "; unidirectional="
              + unidirectional);

      if (isOldAgentToken) {
        if (isNewTransportAgent) {
          agentManager.updateNewTransportAgent(
              agentToken, agentIP, port, args.getAuthToken(), version, unidirectional);
        } else {
          agentManager.updateLegacyAgent(agentToken, agentIP, port, args.getAuthToken(), version);
        }
      } else {
        if (isNewTransportAgent) {
          agentManager.updateNewTransportAgent(
              agentIP, port, args.getAuthToken(), agentToken, version, unidirectional);
        } else {
          agentManager.updateLegacyAgent(agentIP, port, args.getAuthToken(), agentToken, version);
        }
      }
    } catch (AgentNotFoundException exc) {
      log.info(
          "Registering agent at "
              + agentIP
              + ":"
              + port
              + ""
              + "; new transport="
              + isNewTransportAgent
              + "; unidirectional="
              + unidirectional);
      try {
        if (isNewTransportAgent) {
          agentManager.createNewTransportAgent(
              agentIP, new Integer(port), args.getAuthToken(), agentToken, version, unidirectional);
        } else {
          agentManager.createLegacyAgent(
              agentIP, new Integer(port), args.getAuthToken(), agentToken, version);
        }

      } catch (AgentCreateException oexc) {
        log.error("Error creating agent", oexc);
        return new RegisterAgent_result("Error creating agent: " + oexc.getMessage());
      } catch (SystemException oexc) {
        log.error("Error creating agent", oexc);
        return new RegisterAgent_result("Error creating agent:  " + "Internal system error");
      }
    } catch (SystemException exc) {
      log.error("Error updating agent", exc);
      return new RegisterAgent_result("Error updating agent:  " + "Internal system error");
    }

    RegisterAgent_result result = new RegisterAgent_result("token:" + agentToken);
    return result;
  }