content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package org.apache.ambari.server.api.query.render;
  
  import java.util.Set;
  
  import org.apache.ambari.server.api.query.QueryInfo;
  import org.apache.ambari.server.api.services.Request;
  import org.apache.ambari.server.api.services.Result;
  import org.apache.ambari.server.api.services.ResultImpl;
  import org.apache.ambari.server.api.services.ResultPostProcessor;
  import org.apache.ambari.server.api.services.ResultPostProcessorImpl;
  import org.apache.ambari.server.api.util.TreeNode;
  import org.apache.ambari.server.api.util.TreeNodeImpl;
  import org.apache.ambari.server.controller.internal.AlertResourceProvider;
  import org.apache.ambari.server.controller.internal.ResourceImpl;
  import org.apache.ambari.server.controller.spi.Resource;
  import org.apache.ambari.server.state.AlertState;
  import org.apache.ambari.server.state.MaintenanceState;
  
  /**
   * The {@link AlertSummaryRenderer} is used to format the results of queries to
   * the alerts endpoint. Each item returned from the query represents an
   * individual current alert which is then aggregated into a summary structure
   * based on the alert state.
   * <p/>
   * The finalized structure is:
   *
   * <pre>
   * {
   *   "href" : "http://localhost:8080/api/v1/clusters/c1/alerts?format=summary",
   *   "alerts_summary" : {
   *     "CRITICAL" : {
   *       "count" : 3,
   *       "maintenance_count" : 1,
   *       "original_timestamp" : 1415372828182
   *     },
   *     "OK" : {
   *       "count" : 37,
   *       "maintenance_count" : 0,
   *       "original_timestamp" : 1415375364937
   *     },
   *     "UNKNOWN" : {
   *       "count" : 1,
   *       "maintenance_count" : 0,
   *       "original_timestamp" : 1415372632261
   *     },
   *     "WARN" : {
   *       "count" : 0,
   *       "maintenance_count" : 0,
   *       "original_timestamp" : 0
   *     }
   *   }
   * }
   * </pre>
   * <p/>
   * The nature of a {@link Renderer} is that it manipulates the dataset returned
   * by a query. In the case of alert data, the query could potentially return
   * thousands of results if there are thousands of nodes in the cluster. This
   * could present a performance issue that can only be addressed by altering the
   * incoming query and modifying it to instruct the backend to return a JPA SUM
   * instead of a collection of entities.
   */
  public class AlertSummaryRenderer extends BaseRenderer implements Renderer {
  
    /**
     * {@inheritDoc}
     */
    @Override
    public TreeNode<Set<String>> finalizeProperties(
        TreeNode<QueryInfo> queryTree, boolean isCollection) {
  
      QueryInfo queryInfo = queryTree.getObject();
      TreeNode<Set<String>> resultTree = new TreeNodeImpl<>(
        null, queryInfo.getProperties(), queryTree.getName());
  
      copyPropertiesToResult(queryTree, resultTree);
  
      boolean addKeysToEmptyResource = true;
      if (!isCollection && isRequestWithNoProperties(queryTree)) {
        addSubResources(queryTree, resultTree);
        addKeysToEmptyResource = false;
      }
  
      ensureRequiredProperties(resultTree, addKeysToEmptyResource);
  
      // ensure that state and original_timestamp are on the request since these
      // are required by the finalization process of this renderer
      Set<String> properties = resultTree.getObject();
      addRequiredAlertProperties(properties);
  
      return resultTree;
    }
  
    /**
     * {@inheritDoc}
     */
    @Override
    public ResultPostProcessor getResultPostProcessor(Request request) {
      // simply return the native rendering
      return new ResultPostProcessorImpl(request);
    }
  
    /**
     * {@inheritDoc}
     * <p/>
     * This will iterate over all of the nodes in the result tree and combine
     * their {@link AlertResourceProvider#ALERT_STATE} into a single summary
     * structure.
     */
    @Override
    public Result finalizeResult(Result queryResult) {
      TreeNode<Resource> resultTree = queryResult.getResultTree();
      AlertStateSummary alertSummary = new AlertStateSummary();
  
      // iterate over all returned flattened alerts and build the summary info
      for (TreeNode<Resource> node : resultTree.getChildren()) {
        Resource resource = node.getObject();
        AlertState state = (AlertState) resource.getPropertyValue(AlertResourceProvider.ALERT_STATE);
        Long originalTimestampObject = (Long) resource.getPropertyValue(AlertResourceProvider.ALERT_ORIGINAL_TIMESTAMP);
        MaintenanceState maintenanceState = (MaintenanceState) resource.getPropertyValue(AlertResourceProvider.ALERT_MAINTENANCE_STATE);
  
        // NPE sanity
        if (null == state) {
          state = AlertState.UNKNOWN;
        }
  
        // NPE sanity
        long originalTimestamp = 0;
        if (null != originalTimestampObject) {
          originalTimestamp = originalTimestampObject.longValue();
        }
  
        // NPE sanity
        boolean isMaintenanceModeEnabled = false;
        if (null != maintenanceState && maintenanceState != MaintenanceState.OFF) {
          isMaintenanceModeEnabled = true;
        }
  
        final AlertStateValues alertStateValues;
        switch (state) {
          case CRITICAL: {
            alertStateValues = alertSummary.Critical;
            break;
          }
          case OK: {
            alertStateValues = alertSummary.Ok;
            break;
          }
          case WARNING: {
            alertStateValues = alertSummary.Warning;
            break;
          }
          default:
          case UNKNOWN: {
            alertStateValues = alertSummary.Unknown;
            break;
          }
        }
  
        if (isMaintenanceModeEnabled) {
          alertStateValues.MaintenanceCount++;
        } else {
          alertStateValues.Count++;
        }
  
        if (originalTimestamp > alertStateValues.Timestamp) {
          alertStateValues.Timestamp = originalTimestamp;
        }
      }
  
      Result summary = new ResultImpl(true);
      Resource resource = new ResourceImpl(Resource.Type.Alert);
      TreeNode<Resource> summaryTree = summary.getResultTree();
      summaryTree.addChild(resource, "alerts_summary");
      resource.setProperty("alerts_summary", alertSummary);
  
      return summary;
    }
  
    /**
     * Adds properties to the backend request that are required by this renderer.
     * This method currently adds {@link AlertResourceProvider#ALERT_STATE} and
     * {@link AlertResourceProvider#ALERT_ORIGINAL_TIMESTAMP}.
     *
     * @param properties
     *          the properties collection to add to.
     */
    protected void addRequiredAlertProperties(Set<String> properties) {
      properties.add(AlertResourceProvider.ALERT_STATE);
      properties.add(AlertResourceProvider.ALERT_ORIGINAL_TIMESTAMP);
      properties.add(AlertResourceProvider.ALERT_MAINTENANCE_STATE);
    }
  }
  