@Override
  public void process(ClusterEvent event) throws Exception {
    Cluster cluster = event.getAttribute("Cluster");
    if (cluster == null) {
      throw new StageException("Missing attributes in event:" + event + ". Requires Cluster");
    }
    Map<ResourceId, ResourceConfig> resourceConfigMap =
        event.getAttribute(AttributeName.RESOURCES.toString());
    if (resourceConfigMap == null) {
      throw new StageException("Resources must be computed prior to validation!");
    }
    Map<ResourceId, Resource> resourceMap = cluster.getResourceMap();
    Map<String, Map<String, String>> idealStateRuleMap =
        event.getAttribute(AttributeName.IDEAL_STATE_RULES.toString());

    for (ResourceId resourceId : resourceMap.keySet()) {
      // check every ideal state against the ideal state rules
      // the pipeline should not process any resources that have an unsupported ideal state
      IdealState idealState = resourceMap.get(resourceId).getIdealState();
      if (idealState == null) {
        continue;
      }
      if (idealStateRuleMap != null && !idealStateRuleMap.isEmpty()) {
        boolean hasMatchingRule = false;
        for (String ruleName : idealStateRuleMap.keySet()) {
          Map<String, String> rule = idealStateRuleMap.get(ruleName);
          boolean matches = idealStateMatchesRule(idealState, rule);
          hasMatchingRule = hasMatchingRule || matches;
          if (matches) {
            break;
          }
        }
        if (!hasMatchingRule) {
          LOG.warn("Resource " + resourceId + " does not have a valid ideal state!");
          resourceConfigMap.remove(resourceId);
        }
      }

      // check that every resource to process has a live state model definition
      StateModelDefinitionId stateModelDefId = idealState.getStateModelDefId();
      StateModelDefinition stateModelDef = cluster.getStateModelMap().get(stateModelDefId);
      if (stateModelDef == null) {
        LOG.warn(
            "Resource "
                + resourceId
                + " uses state model "
                + stateModelDefId
                + ", but it is not on the cluster!");
        resourceConfigMap.remove(resourceId);
      }
    }
  }
  @Override
  public void process(ClusterEvent event) throws Exception {
    Cluster cluster = event.getAttribute("Cluster");
    Map<StateModelDefId, StateModelDefinition> stateModelDefMap = cluster.getStateModelMap();
    Map<ResourceId, ResourceConfig> resourceMap =
        event.getAttribute(AttributeName.RESOURCES.toString());
    ResourceCurrentState currentStateOutput =
        event.getAttribute(AttributeName.CURRENT_STATE.toString());
    BestPossibleStateOutput bestPossibleStateOutput =
        event.getAttribute(AttributeName.BEST_POSSIBLE_STATE.toString());
    MessageOutput messageGenOutput = event.getAttribute(AttributeName.MESSAGES_ALL.toString());
    if (cluster == null
        || resourceMap == null
        || currentStateOutput == null
        || messageGenOutput == null
        || bestPossibleStateOutput == null) {
      throw new StageException(
          "Missing attributes in event:"
              + event
              + ". Requires Cluster|RESOURCES|CURRENT_STATE|BEST_POSSIBLE_STATE|MESSAGES_ALL");
    }

    MessageOutput output = new MessageOutput();

    for (ResourceId resourceId : resourceMap.keySet()) {
      ResourceConfig resource = resourceMap.get(resourceId);
      StateModelDefinition stateModelDef =
          stateModelDefMap.get(resource.getIdealState().getStateModelDefId());

      if (stateModelDef == null) {
        LOG.info(
            "resource: "
                + resourceId
                + " doesn't have state-model-def; e.g. we add a resource config but not add the resource in ideal-states");
        continue;
      }

      // TODO have a logical model for transition
      Map<String, Integer> stateTransitionPriorities = getStateTransitionPriorityMap(stateModelDef);
      Resource configResource = cluster.getResource(resourceId);

      // if configResource == null, the resource has been dropped
      Map<State, Bounds> stateConstraints =
          computeStateConstraints(
              stateModelDef,
              configResource == null ? null : configResource.getIdealState(),
              cluster);

      // TODO fix it
      for (PartitionId partitionId :
          bestPossibleStateOutput.getResourceAssignment(resourceId).getMappedPartitionIds()) {
        List<Message> messages = messageGenOutput.getMessages(resourceId, partitionId);
        List<Message> selectedMessages =
            selectMessages(
                cluster.getLiveParticipantMap(),
                currentStateOutput.getCurrentStateMap(resourceId, partitionId),
                currentStateOutput.getPendingStateMap(resourceId, partitionId),
                messages,
                stateConstraints,
                stateTransitionPriorities,
                stateModelDef.getTypedInitialState());
        output.setMessages(resourceId, partitionId, selectedMessages);
      }
    }
    event.addAttribute(AttributeName.MESSAGES_SELECTED.toString(), output);
  }