/**
 * 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.hadoop.yarn.server.timelineservice.reader;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;

import javax.inject.Singleton;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.hadoop.classification.InterfaceAudience.Private;
import org.apache.hadoop.classification.InterfaceStability.Unstable;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.http.JettyUtils;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.util.Time;
import org.apache.hadoop.yarn.api.records.timeline.TimelineAbout;
import org.apache.hadoop.yarn.api.records.timeline.TimelineHealth;
import org.apache.hadoop.yarn.api.records.timelineservice.FlowActivityEntity;
import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntity;
import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntityType;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.apache.hadoop.yarn.server.timelineservice.metrics.TimelineReaderMetrics;
import org.apache.hadoop.yarn.server.timelineservice.storage.TimelineReader.Field;
import org.apache.hadoop.yarn.util.timeline.TimelineUtils;
import org.apache.hadoop.yarn.webapp.BadRequestException;
import org.apache.hadoop.yarn.webapp.ForbiddenException;
import org.apache.hadoop.yarn.webapp.NotFoundException;

import org.apache.hadoop.classification.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** REST end point for Timeline Reader. */
@Private
@Unstable
@Singleton
@Path("/ws/v2/timeline")
public class TimelineReaderWebServices {
  private static final Logger LOG =
      LoggerFactory.getLogger(TimelineReaderWebServices.class);

  @Context
  private ServletContext ctxt;

  private static final String QUERY_STRING_SEP = "?";
  private static final String RANGE_DELIMITER = "-";
  private static final String DATE_PATTERN = "yyyyMMdd";
  private static final TimelineReaderMetrics METRICS =
      TimelineReaderMetrics.getInstance();

  @VisibleForTesting
  static final ThreadLocal<DateFormat> DATE_FORMAT =
      new ThreadLocal<DateFormat>() {
      @Override
      protected DateFormat initialValue() {
        SimpleDateFormat format =
            new SimpleDateFormat(DATE_PATTERN, Locale.ENGLISH);
        format.setTimeZone(TimeZone.getTimeZone("GMT"));
        format.setLenient(false);
        return format;
      }
    };

  private void init(HttpServletResponse response) {
    response.setContentType(null);
  }

  private static final class DateRange {
    private Long dateStart;
    private Long dateEnd;
    private DateRange(Long start, Long end) {
      this.dateStart = start;
      this.dateEnd = end;
    }
  }

  private static long parseDate(String strDate) throws ParseException {
    Date date = DATE_FORMAT.get().parse(strDate);
    return date.getTime();
  }

  /**
   * Parses date range which can be a single date or in the format
   * "[startdate]-[enddate]" where either of start or end date may not exist.
   * @param dateRange
   * @return a {@link DateRange} object.
   * @throws IllegalArgumentException
   */
  private static DateRange parseDateRange(String dateRange)
      throws IllegalArgumentException {
    if (dateRange == null || dateRange.isEmpty()) {
      return new DateRange(null, null);
    }
    // Split date range around "-" fetching two components indicating start and
    // end date.
    String[] dates = dateRange.split(RANGE_DELIMITER, 2);
    Long start = null;
    Long end = null;
    try {
      String startDate = dates[0].trim();
      if (!startDate.isEmpty()) {
        // Start date is not in yyyyMMdd format.
        if (startDate.length() != DATE_PATTERN.length()) {
          throw new IllegalArgumentException("Invalid date range " + dateRange);
        }
        // Parse start date which exists before "-" in date range.
        // If "-" does not exist in date range, this effectively
        // gives single date.
        start = parseDate(startDate);
      }
      if (dates.length > 1) {
        String endDate = dates[1].trim();
        if (!endDate.isEmpty()) {
          // End date is not in yyyyMMdd format.
          if (endDate.length() != DATE_PATTERN.length()) {
            throw new IllegalArgumentException(
                "Invalid date range " + dateRange);
          }
          // Parse end date which exists after "-" in date range.
          end = parseDate(endDate);
        }
      } else {
        // Its a single date(without "-" in date range), so set
        // end equal to start.
        end = start;
      }
      if (start != null && end != null) {
        if (start > end) {
          throw new IllegalArgumentException("Invalid date range " + dateRange);
        }
      }
      return new DateRange(start, end);
    } catch (ParseException e) {
      // Date could not be parsed.
      throw new IllegalArgumentException("Invalid date range " + dateRange);
    }
  }

  private TimelineReaderManager getTimelineReaderManager() {
    return (TimelineReaderManager)
        ctxt.getAttribute(TimelineReaderServer.TIMELINE_READER_MANAGER_ATTR);
  }

  private static void handleException(Exception e, String url, long startTime,
      String invalidNumMsg) throws BadRequestException,
      WebApplicationException {
    long endTime = Time.monotonicNow();
    LOG.info("Processed URL {} but encountered exception (Took " +
        "{} ms.)", url, (endTime - startTime));
    if (e instanceof NumberFormatException) {
      throw new BadRequestException(invalidNumMsg + " is not a numeric value.");
    } else if (e instanceof IllegalArgumentException) {
      throw new BadRequestException(e.getMessage() == null ?
          "Requested Invalid Field." : e.getMessage());
    } else if (e instanceof NotFoundException) {
      throw (NotFoundException)e;
    } else if (e instanceof TimelineParseException) {
      throw new BadRequestException(e.getMessage() == null ?
          "Filter Parsing failed." : e.getMessage());
    } else if (e instanceof BadRequestException) {
      throw (BadRequestException)e;
    } else if (e instanceof ForbiddenException) {
      throw (ForbiddenException) e;
    } else {
      LOG.error("Error while processing REST request", e);
      throw new WebApplicationException(e,
          Response.Status.INTERNAL_SERVER_ERROR);
    }
  }

  /**
   * Return the description of the timeline reader web services.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   *
   * @return information about the cluster including timeline version.
   */
  @GET
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public TimelineAbout about(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res) {
    init(res);
    return TimelineUtils.createTimelineAbout("Timeline Reader API");
  }

  /**
   * Health check REST end point.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   *
   * @return A {@link Response} object with HTTP status 200 OK if the service
   *         is running.
   *         Otherwise, a {@link Response} object with HTTP status 500 is
   *         returned.
   */
  @GET
  @Path("/health")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Response health(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res
  ) {
    Response response;
    TimelineHealth timelineHealth = this.getTimelineReaderManager().getHealthStatus();
    if (timelineHealth.getHealthStatus()
        .equals(TimelineHealth.TimelineHealthStatus.RUNNING)) {
      response = Response.ok(timelineHealth).build();
    } else {
       LOG.info("Timeline services health check: timeline reader reported " +
           "connection failure");
       response = Response.serverError().entity(timelineHealth).build();
    }

    return response;
  }

  /**
   * Return a single entity for a given entity type and UID which is a delimited
   * string containing clusterid, userid, flow name, flowrun id and app id.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param uId a delimited string containing clusterid, userid, flow name,
   *     flowrun id and app id which are extracted from UID and then used to
   *     query backend(Mandatory path param).
   * @param entityType Type of entities(Mandatory path param).
   * @param limit If specified, defines the number of entities to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched entities should not be
   *     created before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched entities should not be created
   *     after this timestamp(Optional query param).
   * @param relatesTo If specified, matched entities should relate to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param isRelatedTo If specified, matched entities should be related to
   *     given entities associated with a entity type. relatesto is a comma
   *     separated list in the format [entitytype]:[entityid1]:[entityid2]...
   *     (Optional query param).
   * @param infofilters If specified, matched entities should have exact matches
   *     to the given info represented as key-value pairs. This is represented
   *     as infofilters=info1:value1,info2:value2... (Optional query param).
   * @param conffilters If specified, matched entities should have exact matches
   *     to the given configs represented as key-value pairs. This is
   *     represented as conffilters=conf1:value1,conf2:value2... (Optional query
   *     param).
   * @param metricfilters If specified, matched entities should contain the
   *     given metrics. This is represented as
   *     metricfilters=metricid1, metricid2... (Optional query param).
   * @param eventfilters If specified, matched entities should contain the given
   *     events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *     {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type, id and created time is returned
   *     (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the entities
   *     would not contain metric values before this timestamp(Optional query
   *     param).
   * @param metricsTimeEnd If specified, returned metrics for the entities would
   *     not contain metric values after this timestamp(Optional query param).
   * @param fromId If specified, retrieve the next set of entities from the
   *     given fromId. The set of entities retrieved is inclusive of specified
   *     fromId. fromId should be taken from the value associated with FROM_ID
   *     info key in entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing
   *     a set of <cite>TimelineEntity</cite> instances of the given entity type
   *     is returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request or UID is incorrect,
   *     HTTP 400(Bad Request) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/app-uid/{uid}/entities/{entitytype}")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getEntities(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("uid") String uId,
      @PathParam("entitytype") String entityType,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    Set<TimelineEntity> entities = null;
    try {
      TimelineReaderContext context =
          TimelineUIDConverter.APPLICATION_UID.decodeUID(uId);
      if (context == null) {
        throw new BadRequestException("Incorrect UID " +  uId);
      }
      context.setEntityType(
          TimelineReaderWebServicesUtils.parseStr(entityType));
      context.setGenericEntity(true);
      entities = timelineReaderManager.getEntities(context,
          TimelineReaderWebServicesUtils.createTimelineEntityFilters(
          limit, createdTimeStart, createdTimeEnd, relatesTo, isRelatedTo,
              infofilters, conffilters, metricfilters, eventfilters,
              fromId),
          TimelineReaderWebServicesUtils.createTimelineDataToRetrieve(
          confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
          metricsTimeStart, metricsTimeEnd));
      checkAccessForGenericEntities(entities, callerUGI, entityType);
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime,
          "Either limit or createdtime start/end or metricslimit or metricstime"
              + " start/end or fromid");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {}" +
          " (Took {} ms.)", url, latency);
    }
    if (entities == null) {
      entities = Collections.emptySet();
    }
    return entities;
  }

  /**
   * Return a set of entities that match the given parameters. Cluster ID is not
   * provided by client so default cluster ID has to be taken. If userid, flow
   * name and flowrun id which are optional query parameters are not specified,
   * they will be queried based on app id and default cluster id from the flow
   * context information stored in underlying storage implementation. If number
   * of matching entities are more than the limit, most recent entities till the
   * limit is reached, will be returned.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param appId Application id to which the entities to be queried belong to(
   *     Mandatory path param).
   * @param entityType Type of entities(Mandatory path param).
   * @param userId User id which should match for the entities(Optional query
   *     param)
   * @param flowName Flow name which should match for the entities(Optional
   *     query param).
   * @param flowRunId Run id which should match for the entities(Optional query
   *     param).
   * @param limit If specified, defines the number of entities to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched entities should not be
   *     created before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched entities should not be created
   *     after this timestamp(Optional query param).
   * @param relatesTo If specified, matched entities should relate to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param isRelatedTo If specified, matched entities should be related to
   *     given entities associated with a entity type. relatesto is a comma
   *     separated list in the format [entitytype]:[entityid1]:[entityid2]...
   *     (Optional query param).
   * @param infofilters If specified, matched entities should have exact matches
   *     to the given info represented as key-value pairs. This is represented
   *     as infofilters=info1:value1,info2:value2... (Optional query param).
   * @param conffilters If specified, matched entities should have exact matches
   *     to the given configs represented as key-value pairs. This is
   *     represented as conffilters=conf1:value1,conf2:value2... (Optional query
   *     param).
   * @param metricfilters If specified, matched entities should contain the
   *     given metrics. This is represented as
   *     metricfilters=metricid1, metricid2... (Optional query param).
   * @param eventfilters If specified, matched entities should contain the given
   *     events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *     {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type, id, created time is returned
   *     (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the entities
   *     would not contain metric values before this timestamp(Optional query
   *     param).
   * @param metricsTimeEnd If specified, returned metrics for the entities would
   *     not contain metric values after this timestamp(Optional query param).
   * @param fromId If specified, retrieve the next set of entities from the
   *     given fromId. The set of entities retrieved is inclusive of specified
   *     fromId. fromId should be taken from the value associated with FROM_ID
   *     info key in entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing
   *     a set of <cite>TimelineEntity</cite> instances of the given entity type
   *     is returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     If flow context information cannot be retrieved, HTTP 404(Not Found)
   *     is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/apps/{appid}/entities/{entitytype}")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  @Consumes(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getEntities(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("appid") String appId,
      @PathParam("entitytype") String entityType,
      @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {
    return getEntities(req, res, null, appId, entityType, userId, flowName,
        flowRunId, limit, createdTimeStart, createdTimeEnd, relatesTo,
        isRelatedTo, infofilters, conffilters, metricfilters, eventfilters,
        confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd, fromId, true);
  }

  /**
   * Return a set of entities that match the given parameters. If userid, flow
   * name and flowrun id which are optional query parameters are not specified,
   * they will be queried based on app id and cluster id from the flow context
   * information stored in underlying storage implementation. If number of
   * matching entities are more than the limit, most recent entities till the
   * limit is reached, will be returned.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the entities to be queried belong to(
   *     Mandatory path param).
   * @param appId Application id to which the entities to be queried belong to(
   *     Mandatory path param).
   * @param entityType Type of entities(Mandatory path param).
   * @param userId User id which should match for the entities(Optional query
   *     param)
   * @param flowName Flow name which should match for the entities(Optional
   *     query param).
   * @param flowRunId Run id which should match for the entities(Optional query
   *     param).
   * @param limit If specified, defines the number of entities to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched entities should not be
   *     created before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched entities should not be created
   *     after this timestamp(Optional query param).
   * @param relatesTo If specified, matched entities should relate to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param isRelatedTo If specified, matched entities should be related to
   *     given entities associated with a entity type. relatesto is a comma
   *     separated list in the format [entitytype]:[entityid1]:[entityid2]...
   *     (Optional query param).
   * @param infofilters If specified, matched entities should have exact matches
   *     to the given info represented as key-value pairs. This is represented
   *     as infofilters=info1:value1,info2:value2... (Optional query param).
   * @param conffilters If specified, matched entities should have exact matches
   *     to the given configs represented as key-value pairs. This is
   *     represented as conffilters=conf1:value1,conf2:value2... (Optional query
   *     param).
   * @param metricfilters If specified, matched entities should contain the
   *     given metrics. This is represented as
   *     metricfilters=metricid1, metricid2... (Optional query param).
   * @param eventfilters If specified, matched entities should contain the given
   *     events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *     {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type, id, created time is returned
   *     (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the entities
   *     would not contain metric values before this timestamp(Optional query
   *     param).
   * @param metricsTimeEnd If specified, returned metrics for the entities would
   *     not contain metric values after this timestamp(Optional query param).
   * @param fromId If specified, retrieve the next set of entities from the
   *     given fromId. The set of entities retrieved is inclusive of specified
   *     fromId. fromId should be taken from the value associated with FROM_ID
   *     info key in entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing
   *     a set of <cite>TimelineEntity</cite> instances of the given entity type
   *     is returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     If flow context information cannot be retrieved, HTTP 404(Not Found)
   *     is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/apps/{appid}/entities/{entitytype}")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  @Consumes(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getEntities(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("appid") String appId,
      @PathParam("entitytype") String entityType,
      @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {
    return getEntities(req, res, null, appId, entityType, userId, flowName,
        flowRunId, limit, createdTimeStart, createdTimeEnd, relatesTo,
        isRelatedTo, infofilters, conffilters, metricfilters, eventfilters,
        confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd, fromId, true);
  }

  public Set<TimelineEntity> getEntities(HttpServletRequest req,
      HttpServletResponse res, String clusterId, String appId,
      String entityType, String userId, String flowName,
      String flowRunId, String limit, String createdTimeStart,
      String createdTimeEnd, String relatesTo, String isRelatedTo,
      String infofilters, String conffilters, String metricfilters,
      String eventfilters, String confsToRetrieve, String metricsToRetrieve,
      String fields, String metricsLimit, String metricsTimeStart,
      String metricsTimeEnd, String fromId,
      boolean genericEntity) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    Set<TimelineEntity> entities = null;
    try {
      TimelineReaderContext context = TimelineReaderWebServicesUtils
          .createTimelineReaderContext(clusterId, userId, flowName, flowRunId,
              appId, entityType, null, null);
      context.setGenericEntity(genericEntity);
      entities = timelineReaderManager.getEntities(context,
          TimelineReaderWebServicesUtils
              .createTimelineEntityFilters(limit, createdTimeStart,
                  createdTimeEnd, relatesTo, isRelatedTo, infofilters,
                  conffilters, metricfilters, eventfilters, fromId),
          TimelineReaderWebServicesUtils
              .createTimelineDataToRetrieve(confsToRetrieve, metricsToRetrieve,
                  fields, metricsLimit, metricsTimeStart, metricsTimeEnd));

      checkAccessForGenericEntities(entities, callerUGI, entityType);
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime,
          "Either flowrunid or limit or createdtime start/end or metricslimit"
              + " or metricstime start/end or fromid");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entities == null) {
      entities = Collections.emptySet();
    }
    return entities;
  }

  /**
   * Return a single entity for given UID which is a delimited string containing
   * clusterid, userid, flow name, flowrun id, app id, entity type and entityid.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param uId a delimited string containing clusterid, userid, flow name,
   *     flowrun id, app id, entity type and entity id which are extracted from
   *     UID and then used to query backend(Mandatory path param).
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *     {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type, id, created time is returned
   *     (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the entity would
   *     not contain metric values before this timestamp(Optional query param).
   * @param metricsTimeEnd If specified, returned metrics for the entity would
   *     not contain metric values after this timestamp(Optional query param).
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     <cite>TimelineEntity</cite> instance is returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request or UID is incorrect,
   *     HTTP 400(Bad Request) is returned.<br>
   *     If entity for the given entity id cannot be found, HTTP 404(Not Found)
   *     is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/entity-uid/{uid}/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  @Consumes(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public TimelineEntity getEntity(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("uid") String uId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    TimelineEntity entity = null;
    try {
      TimelineReaderContext context =
          TimelineUIDConverter.GENERIC_ENTITY_UID.decodeUID(uId);
      if (context == null) {
        throw new BadRequestException("Incorrect UID " +  uId);
      }
      entity = timelineReaderManager.getEntity(context,
          TimelineReaderWebServicesUtils.createTimelineDataToRetrieve(
          confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
          metricsTimeStart, metricsTimeEnd));
      checkAccessForGenericEntity(entity, callerUGI);
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime, "Either metricslimit or metricstime"
          + " start/end");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entity == null) {
      LOG.info("Processed URL {} but entity not found" + " (Took {} ms.)",
          url, (Time.monotonicNow() - startTime));
      throw new NotFoundException("Timeline entity with uid: " + uId +
          "is not found");
    }
    return entity;
  }

  public TimelineEntity getEntity(HttpServletRequest req,
      HttpServletResponse res, String clusterId, String appId,
      String entityType, String entityId, String userId, String flowName,
      String flowRunId, String confsToRetrieve, String metricsToRetrieve,
      String fields, String metricsLimit, String metricsTimeStart,
      String metricsTimeEnd, String entityIdPrefix,
      boolean genericEntity) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    TimelineEntity entity = null;
    TimelineReaderContext context = TimelineReaderWebServicesUtils.
        createTimelineReaderContext(clusterId, userId, flowName, flowRunId,
        appId, entityType, entityIdPrefix, entityId);
    context.setGenericEntity(genericEntity);
    try {
      entity = timelineReaderManager.getEntity(context,
          TimelineReaderWebServicesUtils.createTimelineDataToRetrieve(
          confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
          metricsTimeStart, metricsTimeEnd));
      checkAccessForGenericEntity(entity, callerUGI);
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime, "Either flowrunid or metricslimit or"
              + " metricstime start/end");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entity == null) {
      LOG.info("Processed URL {} but entity not found" + " (Took " +
          "{} ms.)", url, (Time.monotonicNow() - startTime));
      throw new NotFoundException("Timeline entity {id: " + entityId +
          ", type: " + entityType + " } is not found");
    }
    return entity;
  }

  /**
   * Return a single entity of the given entity type and Id. Cluster ID is not
   * provided by client so default cluster ID has to be taken. If userid, flow
   * name and flowrun id which are optional query parameters are not specified,
   * they will be queried based on app id and default cluster id from the flow
   * context information stored in underlying storage implementation.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param appId Application id to which the entity to be queried belongs to(
   *     Mandatory path param).
   * @param entityType Type of entity(Mandatory path param).
   * @param entityId Id of the entity to be fetched(Mandatory path param).
   * @param userId User id which should match for the entity(Optional query
   *     param).
   * @param flowName Flow name which should match for the entity(Optional query
   *     param).
   * @param flowRunId Run id which should match for the entity(Optional query
   *     param).
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *     {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type, id, created time is returned
   *     (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the entity would
   *     not contain metric values before this timestamp(Optional query param).
   * @param metricsTimeEnd If specified, returned metrics for the entity would
   *     not contain metric values after this timestamp(Optional query param).
   * @param entityIdPrefix Defines the id prefix for the entity to be fetched.
   *     If specified, then entity retrieval will be faster.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     <cite>TimelineEntity</cite> instance is returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     If flow context information cannot be retrieved or entity for the given
   *     entity id cannot be found, HTTP 404(Not Found) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/apps/{appid}/entities/{entitytype}/{entityid}/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  @Consumes(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public TimelineEntity getEntity(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("appid") String appId,
      @PathParam("entitytype") String entityType,
      @PathParam("entityid") String entityId,
      @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("entityidprefix") String entityIdPrefix) {
    return getEntity(req, res, null, appId, entityType, entityId, userId,
        flowName, flowRunId, confsToRetrieve, metricsToRetrieve, fields,
        metricsLimit, metricsTimeStart, metricsTimeEnd, entityIdPrefix, true);
  }

  /**
   * Return a single entity of the given entity type and Id. If userid, flowname
   * and flowrun id which are optional query parameters are not specified, they
   * will be queried based on app id and cluster id from the flow context
   * information stored in underlying storage implementation.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the entity to be queried belongs to(
   *     Mandatory path param).
   * @param appId Application id to which the entity to be queried belongs to(
   *     Mandatory path param).
   * @param entityType Type of entity(Mandatory path param).
   * @param entityId Id of the entity to be fetched(Mandatory path param).
   * @param userId User id which should match for the entity(Optional query
   *     param).
   * @param flowName Flow name which should match for the entity(Optional query
   *     param).
   * @param flowRunId Run id which should match for the entity(Optional query
   *     param).
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *     {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type, id and created time is returned
   *     (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the entity would
   *     not contain metric values before this timestamp(Optional query param).
   * @param metricsTimeEnd If specified, returned metrics for the entity would
   *     not contain metric values after this timestamp(Optional query param).
   * @param entityIdPrefix Defines the id prefix for the entity to be fetched.
   *     If specified, then entity retrieval will be faster.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     <cite>TimelineEntity</cite> instance is returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     If flow context information cannot be retrieved or entity for the given
   *     entity id cannot be found, HTTP 404(Not Found) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/apps/{appid}/entities/{entitytype}/{entityid}/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  @Consumes(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public TimelineEntity getEntity(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("appid") String appId,
      @PathParam("entitytype") String entityType,
      @PathParam("entityid") String entityId,
      @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("entityidprefix") String entityIdPrefix) {
    return getEntity(req, res, clusterId, appId, entityType, entityId,
        userId, flowName, flowRunId, confsToRetrieve, metricsToRetrieve, fields,
        metricsLimit, metricsTimeStart, metricsTimeEnd, entityIdPrefix, true);
  }

  /**
   * Return a single flow run for given UID which is a delimited string
   * containing clusterid, userid, flow name and flowrun id.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param uId a delimited string containing clusterid, userid, flow name and
   *     flowrun id which are extracted from UID and then used to query backend
   *     (Mandatory path param).
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     <cite>FlowRunEntity</cite> instance is returned. By default, all
   *     metrics for the flow run will be returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request or UID is incorrect,
   *     HTTP 400(Bad Request) is returned.<br>
   *     If flow run for the given flow run id cannot be found, HTTP 404
   *     (Not Found) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/run-uid/{uid}/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public TimelineEntity getFlowRun(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("uid") String uId,
      @QueryParam("metricstoretrieve") String metricsToRetrieve) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    TimelineEntity entity = null;
    try {
      TimelineReaderContext context =
          TimelineUIDConverter.FLOWRUN_UID.decodeUID(uId);
      if (context == null) {
        throw new BadRequestException("Incorrect UID " +  uId);
      }
      // TODO to be removed or modified once ACL story is played
      checkAccess(timelineReaderManager, callerUGI, context.getUserId());
      context.setEntityType(TimelineEntityType.YARN_FLOW_RUN.toString());
      entity = timelineReaderManager.getEntity(context,
          TimelineReaderWebServicesUtils.createTimelineDataToRetrieve(
          null, metricsToRetrieve, null, null, null, null));
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime, "flowrunid");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entity == null) {
      LOG.info("Processed URL {} but flowrun not found (Took {} ms.)",
          url, (Time.monotonicNow() - startTime));
      throw new NotFoundException("Flowrun with uid: " + uId + "is not found");
    }
    return entity;
  }

  /**
   * Return a single flow run for the given user, flow name and run id.
   * Cluster ID is not provided by client so default cluster ID has to be taken.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param userId User id representing the user who ran the flow run(Mandatory
   *     path param).
   * @param flowName Flow name to which the flow run to be queried belongs to(
   *     Mandatory path param).
   * @param flowRunId Id of the flow run to be queried(Mandatory path param).
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     <cite>FlowRunEntity</cite> instance is returned. By default, all
   *     metrics for the flow run will be returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     If flow run for the given flow run id cannot be found, HTTP 404
   *     (Not Found) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/users/{userid}/flows/{flowname}/runs/{flowrunid}/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public TimelineEntity getFlowRun(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("userid") String userId,
      @PathParam("flowname") String flowName,
      @PathParam("flowrunid") String flowRunId,
      @QueryParam("metricstoretrieve") String metricsToRetrieve) {
    return getFlowRun(req, res, null, userId, flowName, flowRunId,
        metricsToRetrieve);
  }

  /**
   * Return a single flow run for the given user, cluster, flow name and run id.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the flow run to be queried belong to(
   *     Mandatory path param).
   * @param userId User id representing the user who ran the flow run(Mandatory
   *     path param).
   * @param flowName Flow name to which the flow run to be queried belongs to(
   *     Mandatory path param).
   * @param flowRunId Id of the flow run to be queried(Mandatory path param).
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     <cite>FlowRunEntity</cite> instance is returned. By default, all
   *     metrics for the flow run will be returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     If flow run for the given flow run id cannot be found, HTTP 404
   *     (Not Found) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/users/{userid}/flows/{flowname}/"
      + "runs/{flowrunid}/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public TimelineEntity getFlowRun(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("userid") String userId,
      @PathParam("flowname") String flowName,
      @PathParam("flowrunid") String flowRunId,
      @QueryParam("metricstoretrieve") String metricsToRetrieve) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    TimelineEntity entity = null;
    try {
      TimelineReaderContext context = TimelineReaderWebServicesUtils
          .createTimelineReaderContext(clusterId, userId, flowName, flowRunId,
              null, TimelineEntityType.YARN_FLOW_RUN.toString(), null, null);
      // TODO to be removed or modified once ACL story is played
      checkAccess(timelineReaderManager, callerUGI, context.getUserId());

      entity = timelineReaderManager.getEntity(context,
          TimelineReaderWebServicesUtils
              .createTimelineDataToRetrieve(null, metricsToRetrieve, null, null,
                  null, null));
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime, "flowrunid");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entity == null) {
      LOG.info("Processed URL {} but flowrun not found (Took {} ms.)",
          url, (Time.monotonicNow() - startTime));
      throw new NotFoundException("Flow run {flow name: " +
          TimelineReaderWebServicesUtils.parseStr(flowName) + ", run id: " +
          TimelineReaderWebServicesUtils.parseLongStr(flowRunId) +
          " } is not found");
    }
    return entity;
  }

  /**
   * Return a list of flow runs for given UID which is a delimited string
   * containing clusterid, userid and flow name.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param uId a delimited string containing clusterid, userid, and flow name
   *     which are extracted from UID and then used to query backend(Mandatory
   *     path param).
   * @param limit If specified, defines the number of flow runs to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched flow runs should not be
   *     created before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched flow runs should not be created
   *     after this timestamp(Optional query param).
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields to retrieve, see {@link Field}. All
   *     fields will be retrieved if fields=ALL. Amongst all the fields, only
   *     METRICS makes sense for flow runs hence only ALL or METRICS are
   *     supported as fields for fetching flow runs. Other fields will lead to
   *     HTTP 400 (Bad Request) response. (Optional query param).
   * @param fromId If specified, retrieve the next set of flow run entities
   *     from the given fromId. The set of entities retrieved is inclusive of
   *     specified fromId. fromId should be taken from the value associated
   *     with FROM_ID info key in entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     set of <cite>FlowRunEntity</cite> instances for the given flow are
   *     returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request or UID is incorrect,
   *     HTTP 400(Bad Request) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/flow-uid/{uid}/runs/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getFlowRuns(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("uid") String uId,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("fromid") String fromId) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    Set<TimelineEntity> entities = null;
    try {
      TimelineReaderContext context =
          TimelineUIDConverter.FLOW_UID.decodeUID(uId);
      if (context == null) {
        throw new BadRequestException("Incorrect UID " +  uId);
      }
      // TODO to be removed or modified once ACL story is played
      checkAccess(timelineReaderManager, callerUGI, context.getUserId());
      context.setEntityType(TimelineEntityType.YARN_FLOW_RUN.toString());
      entities = timelineReaderManager.getEntities(context,
          TimelineReaderWebServicesUtils.createTimelineEntityFilters(
          limit, createdTimeStart, createdTimeEnd, null, null, null,
              null, null, null, fromId),
          TimelineReaderWebServicesUtils.createTimelineDataToRetrieve(
          null, metricsToRetrieve, fields, null, null, null));
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime,
          "createdTime start/end or limit or fromId");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entities == null) {
      entities = Collections.emptySet();
    }
    return entities;
  }

  /**
   * Return a set of flows runs for the given user and flow name.
   * Cluster ID is not provided by client so default cluster ID has to be taken.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param userId User id representing the user who ran the flow runs(
   *     Mandatory path param)
   * @param flowName Flow name to which the flow runs to be queried belongs to(
   *     Mandatory path param).
   * @param limit If specified, defines the number of flow runs to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched flow runs should not be
   *     created before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched flow runs should not be created
   *     after this timestamp(Optional query param).
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields to retrieve, see {@link Field}. All
   *     fields will be retrieved if fields=ALL. Amongst all the fields, only
   *     METRICS makes sense for flow runs hence only ALL or METRICS are
   *     supported as fields for fetching flow runs. Other fields will lead to
   *     HTTP 400 (Bad Request) response. (Optional query param).
   * @param fromId If specified, retrieve the next set of flow run entities
   *     from the given fromId. The set of entities retrieved is inclusive of
   *     specified fromId. fromId should be taken from the value associated
   *     with FROM_ID info key in entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     set of <cite>FlowRunEntity</cite> instances for the given flow are
   *     returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/users/{userid}/flows/{flowname}/runs/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getFlowRuns(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("userid") String userId,
      @PathParam("flowname") String flowName,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("fromid") String fromId) {
    return getFlowRuns(req, res, null, userId, flowName, limit,
        createdTimeStart, createdTimeEnd, metricsToRetrieve, fields, fromId);
  }

  /**
   * Return a set of flows runs for the given cluster, user and flow name.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the flow runs to be queried belong to(
   *     Mandatory path param).
   * @param userId User id representing the user who ran the flow runs(
   *     Mandatory path param)
   * @param flowName Flow name to which the flow runs to be queried belongs to(
   *     Mandatory path param).
   * @param limit If specified, defines the number of flow runs to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched flow runs should not be
   *     created before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched flow runs should not be created
   *     after this timestamp(Optional query param).
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields to retrieve, see {@link Field}. All
   *     fields will be retrieved if fields=ALL. Amongst all the fields, only
   *     METRICS makes sense for flow runs hence only ALL or METRICS are
   *     supported as fields for fetching flow runs. Other fields will lead to
   *     HTTP 400 (Bad Request) response. (Optional query param).
   * @param fromId If specified, retrieve the next set of flow run entities
   *     from the given fromId. The set of entities retrieved is inclusive of
   *     specified fromId. fromId should be taken from the value associated
   *     with FROM_ID info key in entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     set of <cite>FlowRunEntity</cite> instances for the given flow are
   *     returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/users/{userid}/flows/{flowname}/runs/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getFlowRuns(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("userid") String userId,
      @PathParam("flowname") String flowName,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("fromid") String fromId) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    Set<TimelineEntity> entities = null;
    try {
      TimelineReaderContext timelineReaderContext = TimelineReaderWebServicesUtils
          .createTimelineReaderContext(clusterId, userId, flowName, null,
              null, TimelineEntityType.YARN_FLOW_RUN.toString(), null,
              null);
      // TODO to be removed or modified once ACL story is played
      checkAccess(timelineReaderManager, callerUGI,
          timelineReaderContext.getUserId());

      entities = timelineReaderManager.getEntities(timelineReaderContext,
          TimelineReaderWebServicesUtils
              .createTimelineEntityFilters(limit, createdTimeStart,
                  createdTimeEnd, null, null, null, null, null, null, fromId),
          TimelineReaderWebServicesUtils
              .createTimelineDataToRetrieve(null, metricsToRetrieve, fields,
                  null, null, null));
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime,
          "createdTime start/end or limit or fromId");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entities == null) {
      entities = Collections.emptySet();
    }
    return entities;
  }

  /**
   * Return a list of active flows. Cluster ID is not provided by client so
   * default cluster ID has to be taken.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param limit If specified, defines the number of flows to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param dateRange If specified is given as "[startdate]-[enddate]"(i.e.
   *     start and end date separated by "-") or single date. Dates are
   *     interpreted in yyyyMMdd format and are assumed to be in GMT(Optional
   *     query param).
   *     If a single date is specified, all flows active on that date are
   *     returned. If both startdate and enddate is given, all flows active
   *     between start and end date will be returned. If only startdate is
   *     given, flows active on and after startdate are returned. If only
   *     enddate is given, flows active on and before enddate are returned.
   *     For example :
   *     "daterange=20150711" returns flows active on 20150711.
   *     "daterange=20150711-20150714" returns flows active between these
   *     2 dates.
   *     "daterange=20150711-" returns flows active on and after 20150711.
   *     "daterange=-20150711" returns flows active on and before 20150711.
   * @param fromId If specified, retrieve the next set of flows from the given
   *     fromId. The set of flows retrieved is inclusive of specified fromId.
   *     fromId should be taken from the value associated with FROM_ID info key
   *     in flow entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     set of <cite>FlowActivityEntity</cite> instances are returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.<br>
   */
  @GET
  @Path("/flows/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getFlows(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @QueryParam("limit") String limit,
      @QueryParam("daterange") String dateRange,
      @QueryParam("fromid") String fromId) {
    return getFlows(req, res, null, limit, dateRange, fromId);
  }

  /**
   * Return a list of active flows for a given cluster id.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the flows to be queried belong to(
   *     Mandatory path param).
   * @param limit If specified, defines the number of flows to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param dateRange If specified is given as "[startdate]-[enddate]"(i.e.
   *     start and end date separated by "-") or single date. Dates are
   *     interpreted in yyyyMMdd format and are assumed to be in GMT(Optional
   *     query param).
   *     If a single date is specified, all flows active on that date are
   *     returned. If both startdate and enddate is given, all flows active
   *     between start and end date will be returned. If only startdate is
   *     given, flows active on and after startdate are returned. If only
   *     enddate is given, flows active on and before enddate are returned.
   *     For example :
   *     "daterange=20150711" returns flows active on 20150711.
   *     "daterange=20150711-20150714" returns flows active between these
   *     2 dates.
   *     "daterange=20150711-" returns flows active on and after 20150711.
   *     "daterange=-20150711" returns flows active on and before 20150711.
   * @param fromId If specified, retrieve the next set of flows from the given
   *     fromId. The set of flows retrieved is inclusive of specified fromId.
   *     fromId should be taken from the value associated with FROM_ID info key
   *     in flow entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     set of <cite>FlowActivityEntity</cite> instances are returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/flows/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getFlows(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @QueryParam("limit") String limit,
      @QueryParam("daterange") String dateRange,
      @QueryParam("fromid") String fromId) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    Set<TimelineEntity> entities = null;
    try {
      DateRange range = parseDateRange(dateRange);
      TimelineEntityFilters entityFilters =
          TimelineReaderWebServicesUtils.createTimelineEntityFilters(
              limit, range.dateStart, range.dateEnd,
              null, null, null, null, null, null, fromId);
      entities = timelineReaderManager.getEntities(
          TimelineReaderWebServicesUtils.createTimelineReaderContext(
          clusterId, null, null, null, null,
              TimelineEntityType.YARN_FLOW_ACTIVITY.toString(), null, null),
          entityFilters, TimelineReaderWebServicesUtils.
              createTimelineDataToRetrieve(null, null, null, null, null, null));
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime, "limit");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entities == null) {
      entities = Collections.emptySet();
    } else {
      checkAccess(timelineReaderManager, callerUGI, entities,
          FlowActivityEntity.USER_INFO_KEY, true);
    }
    return entities;
  }

  /**
   * Return a single app for given UID which is a delimited string containing
   * clusterid, userid, flow name, flowrun id and app id.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param uId a delimited string containing clusterid, userid, flow name, flow
   *     run id and app id which are extracted from UID and then used to query
   *     backend(Mandatory path param).
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the app entity object to retrieve,
   *     see {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type(equivalent to YARN_APPLICATION),
   *     app id and app created time is returned(Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the apps would
   *     not contain metric values before this timestamp(Optional query param).
   * @param metricsTimeEnd If specified, returned metrics for the apps would
   *     not contain metric values after this timestamp(Optional query param).
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     <cite>TimelineEntity</cite> instance is returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request or UID is incorrect,
   *     HTTP 400(Bad Request) is returned.<br>
   *     If app for the given app id cannot be found, HTTP 404(Not Found) is
   *     returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/app-uid/{uid}/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public TimelineEntity getApp(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("uid") String uId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    TimelineEntity entity = null;
    try {
      TimelineReaderContext context =
          TimelineUIDConverter.APPLICATION_UID.decodeUID(uId);
      if (context == null) {
        throw new BadRequestException("Incorrect UID " +  uId);
      }
      context.setEntityType(TimelineEntityType.YARN_APPLICATION.toString());
      entity = timelineReaderManager.getEntity(context,
          TimelineReaderWebServicesUtils.createTimelineDataToRetrieve(
          confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
          metricsTimeStart, metricsTimeEnd));
      checkAccessForAppEntity(entity, callerUGI);
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime, "Either metricslimit or metricstime"
          + " start/end");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entity == null) {
      LOG.info("Processed URL {} but app not found" + " (Took " +
          "{} ms.)", url, (Time.monotonicNow() - startTime));
      throw new NotFoundException("App with uid " + uId + " not found");
    }
    return entity;
  }

  /**
   * Return a single app for given app id. Cluster ID is not provided by client
   * client so default cluster ID has to be taken. If userid, flow name and flow
   * run id which are optional query parameters are not specified, they will be
   * queried based on app id and cluster id from the flow context information
   * stored in underlying storage implementation.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param appId Application id to be queried(Mandatory path param).
   * @param flowName Flow name which should match for the app(Optional query
   *     param).
   * @param flowRunId Run id which should match for the app(Optional query
   *     param).
   * @param userId User id which should match for the app(Optional query param).
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the app entity object to retrieve,
   *     see {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type(equivalent to YARN_APPLICATION),
   *     app id and app created time is returned(Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the app would
   *     not contain metric values before this timestamp(Optional query param).
   * @param metricsTimeEnd If specified, returned metrics for the app would
   *     not contain metric values after this timestamp(Optional query param).
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     <cite>TimelineEntity</cite> instance is returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     If flow context information cannot be retrieved or app for the given
   *     app id cannot be found, HTTP 404(Not Found) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/apps/{appid}/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public TimelineEntity getApp(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("appid") String appId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("userid") String userId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd) {
    return getApp(req, res, null, appId, flowName, flowRunId, userId,
        confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd);
  }

  /**
   * Return a single app for given cluster id and app id. If userid, flow name
   * and flowrun id which are optional query parameters are not specified, they
   * will be queried based on app id and cluster id from the flow context
   * information stored in underlying storage implementation.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the app to be queried belong to(
   *     Mandatory path param).
   * @param appId Application id to be queried(Mandatory path param).
   * @param flowName Flow name which should match for the app(Optional query
   *     param).
   * @param flowRunId Run id which should match for the app(Optional query
   *     param).
   * @param userId User id which should match for the app(Optional query param).
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the app entity object to retrieve,
   *     see {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type(equivalent to YARN_APPLICATION),
   *     app id and app created time is returned(Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the app would
   *     not contain metric values before this timestamp(Optional query param).
   * @param metricsTimeEnd If specified, returned metrics for the app would
   *     not contain metric values after this timestamp(Optional query param).
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     <cite>TimelineEntity</cite> instance is returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     If flow context information cannot be retrieved or app for the given
   *     app id cannot be found, HTTP 404(Not Found) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/apps/{appid}/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public TimelineEntity getApp(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("appid") String appId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("userid") String userId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    TimelineEntity entity = null;
    try {
      entity = timelineReaderManager.getEntity(
          TimelineReaderWebServicesUtils.createTimelineReaderContext(
          clusterId, userId, flowName, flowRunId, appId,
              TimelineEntityType.YARN_APPLICATION.toString(), null, null),
          TimelineReaderWebServicesUtils.createTimelineDataToRetrieve(
          confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
          metricsTimeStart, metricsTimeEnd));
      checkAccessForAppEntity(entity, callerUGI);
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime, "Either flowrunid or metricslimit or"
          + " metricstime start/end");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entity == null) {
      LOG.info("Processed URL {} but app not found" + " (Took " +
          "{} ms.)", url, (Time.monotonicNow() - startTime));
      throw new NotFoundException("App " + appId + " not found");
    }
    return entity;
  }

  /**
   * Return a list of apps for given UID which is a delimited string containing
   * clusterid, userid, flow name and flowrun id. If number of matching apps are
   * more than the limit, most recent apps till the limit is reached, will be
   * returned.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param uId a delimited string containing clusterid, userid, flow name and
   *     flowrun id which are extracted from UID and then used to query backend.
   *     (Mandatory path param).
   * @param limit If specified, defines the number of apps to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched apps should not be created
   *     before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched apps should not be created
   *     after this timestamp(Optional query param).
   * @param relatesTo If specified, matched apps should relate to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param isRelatedTo If specified, matched apps should be related to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param infofilters If specified, matched apps should have exact matches
   *     to the given info represented as key-value pairs. This is represented
   *     as infofilters=info1:value1,info2:value2... (Optional query param).
   * @param conffilters If specified, matched apps should have exact matches
   *     to the given configs represented as key-value pairs. This is
   *     represented as conffilters=conf1:value1,conf2:value2... (Optional query
   *     param).
   * @param metricfilters If specified, matched apps should contain the given
   *     metrics. This is represented as metricfilters=metricid1, metricid2...
   *     (Optional query param).
   * @param eventfilters If specified, matched apps should contain the given
   *     events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the app entity object to retrieve,
   *     see {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type(equivalent to YARN_APPLICATION),
   *     app id and app created time is returned(Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the apps would
   *     not contain metric values before this timestamp(Optional query param).
   * @param metricsTimeEnd If specified, returned metrics for the apps would
   *     not contain metric values after this timestamp(Optional query param).
   * @param fromId If specified, retrieve the next set of applications
   *     from the given fromId. The set of applications retrieved is inclusive
   *     of specified fromId. fromId should be taken from the value associated
   *     with FROM_ID info key in entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing
   *     a set of <cite>TimelineEntity</cite> instances representing apps is
   *     returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request or UID is incorrect,
   *     HTTP 400(Bad Request) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/run-uid/{uid}/apps")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getFlowRunApps(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("uid") String uId,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    Set<TimelineEntity> entities = null;
    try {
      TimelineReaderContext context =
          TimelineUIDConverter.FLOWRUN_UID.decodeUID(uId);
      if (context == null) {
        throw new BadRequestException("Incorrect UID " +  uId);
      }
      // TODO to be removed or modified once ACL story is played
      checkAccess(timelineReaderManager, callerUGI, context.getUserId());
      context.setEntityType(TimelineEntityType.YARN_APPLICATION.toString());
      entities = timelineReaderManager.getEntities(context,
          TimelineReaderWebServicesUtils.createTimelineEntityFilters(
          limit, createdTimeStart, createdTimeEnd, relatesTo, isRelatedTo,
              infofilters, conffilters, metricfilters, eventfilters,
              fromId),
          TimelineReaderWebServicesUtils.createTimelineDataToRetrieve(
          confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
          metricsTimeStart, metricsTimeEnd));
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime,
          "Either limit or createdtime start/end or metricslimit or"
              + " metricstime start/end");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entities == null) {
      entities = Collections.emptySet();
    }
    return entities;
  }

  /**
   * Return a list of apps for given user, flow name and flow run id. Cluster ID
   * is not provided by client so default cluster ID has to be taken. If number
   * of matching apps are more than the limit, most recent apps till the limit
   * is reached, will be returned.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param userId User id which should match for the apps(Mandatory path param)
   * @param flowName Flow name which should match for the apps(Mandatory path
   *     param).
   * @param flowRunId Run id which should match for the apps(Mandatory path
   *     param).
   * @param limit If specified, defines the number of apps to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched apps should not be created
   *     before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched apps should not be created
   *     after this timestamp(Optional query param).
   * @param relatesTo If specified, matched apps should relate to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param isRelatedTo If specified, matched apps should be related to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param infofilters If specified, matched apps should have exact matches
   *     to the given info represented as key-value pairs. This is represented
   *     as infofilters=info1:value1,info2:value2... (Optional query param).
   * @param conffilters If specified, matched apps should have exact matches
   *     to the given configs represented as key-value pairs. This is
   *     represented as conffilters=conf1:value1,conf2:value2... (Optional query
   *     param).
   * @param metricfilters If specified, matched apps should contain the given
   *     metrics. This is represented as metricfilters=metricid1, metricid2...
   *     (Optional query param).
   * @param eventfilters If specified, matched apps should contain the given
   *     events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the app entity object to retrieve,
   *     see {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type(equivalent to YARN_APPLICATION),
   *     app id and app created time is returned(Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the apps would
   *     not contain metric values before this timestamp(Optional query param).
   * @param metricsTimeEnd If specified, returned metrics for the apps would
   *     not contain metric values after this timestamp(Optional query param).
   * @param fromId If specified, retrieve the next set of applications
   *     from the given fromId. The set of applications retrieved is inclusive
   *     of specified fromId. fromId should be taken from the value associated
   *     with FROM_ID info key in entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing
   *     a set of <cite>TimelineEntity</cite> instances representing apps is
   *     returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/users/{userid}/flows/{flowname}/runs/{flowrunid}/apps/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getFlowRunApps(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("userid") String userId,
      @PathParam("flowname") String flowName,
      @PathParam("flowrunid") String flowRunId,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {
    return getEntities(req, res, null, null,
        TimelineEntityType.YARN_APPLICATION.toString(), userId, flowName,
        flowRunId, limit, createdTimeStart, createdTimeEnd, relatesTo,
        isRelatedTo, infofilters, conffilters, metricfilters, eventfilters,
        confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd, fromId, false);
  }

  /**
   * Return a list of apps for a given user, cluster id, flow name and flow run
   * id. If number of matching apps are more than the limit, most recent apps
   * till the limit is reached, will be returned.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the apps to be queried belong to
   *     (Mandatory path param).
   * @param userId User id which should match for the apps(Mandatory path param)
   * @param flowName Flow name which should match for the apps(Mandatory path
   *     param).
   * @param flowRunId Run id which should match for the apps(Mandatory path
   *     param).
   * @param limit If specified, defines the number of apps to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched apps should not be created
   *     before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched apps should not be created
   *     after this timestamp(Optional query param).
   * @param relatesTo If specified, matched apps should relate to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param isRelatedTo If specified, matched apps should be related to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param infofilters If specified, matched apps should have exact matches
   *     to the given info represented as key-value pairs. This is represented
   *     as infofilters=info1:value1,info2:value2... (Optional query param).
   * @param conffilters If specified, matched apps should have exact matches
   *     to the given configs represented as key-value pairs. This is
   *     represented as conffilters=conf1:value1,conf2:value2... (Optional query
   *     param).
   * @param metricfilters If specified, matched apps should contain the given
   *     metrics. This is represented as metricfilters=metricid1, metricid2...
   *     (Optional query param).
   * @param eventfilters If specified, matched apps should contain the given
   *     events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the app entity object to retrieve,
   *     see {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type(equivalent to YARN_APPLICATION),
   *     app id and app created time is returned(Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the apps would
   *     not contain metric values before this timestamp(Optional query param).
   * @param metricsTimeEnd If specified, returned metrics for the apps would
   *     not contain metric values after this timestamp(Optional query param).
   * @param fromId If specified, retrieve the next set of applications
   *     from the given fromId. The set of applications retrieved is inclusive
   *     of specified fromId. fromId should be taken from the value associated
   *     with FROM_ID info key in entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing
   *     a set of <cite>TimelineEntity</cite> instances representing apps is
   *     returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/users/{userid}/flows/{flowname}/runs/"
      + "{flowrunid}/apps/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getFlowRunApps(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("userid") String userId,
      @PathParam("flowname") String flowName,
      @PathParam("flowrunid") String flowRunId,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {
    return getEntities(req, res, clusterId, null,
        TimelineEntityType.YARN_APPLICATION.toString(), userId, flowName,
        flowRunId, limit, createdTimeStart, createdTimeEnd, relatesTo,
        isRelatedTo, infofilters, conffilters, metricfilters, eventfilters,
        confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd, fromId, false);
  }

  /**
   * Return a list of apps for given user and flow name. Cluster ID is not
   * provided by client so default cluster ID has to be taken. If number of
   * matching apps are more than the limit, most recent apps till the limit is
   * reached, will be returned.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param userId User id which should match for the apps(Mandatory path param)
   * @param flowName Flow name which should match for the apps(Mandatory path
   *     param).
   * @param limit If specified, defines the number of apps to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched apps should not be created
   *     before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched apps should not be created
   *     after this timestamp(Optional query param).
   * @param relatesTo If specified, matched apps should relate to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param isRelatedTo If specified, matched apps should be related to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param infofilters If specified, matched apps should have exact matches
   *     to the given info represented as key-value pairs. This is represented
   *     as infofilters=info1:value1,info2:value2... (Optional query param).
   * @param conffilters If specified, matched apps should have exact matches
   *     to the given configs represented as key-value pairs. This is
   *     represented as conffilters=conf1:value1,conf2:value2... (Optional query
   *     param).
   * @param metricfilters If specified, matched apps should contain the given
   *     metrics. This is represented as metricfilters=metricid1, metricid2...
   *     (Optional query param).
   * @param eventfilters If specified, matched apps should contain the given
   *     events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the app entity object to retrieve,
   *     see {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type(equivalent to YARN_APPLICATION),
   *     app id and app created time is returned(Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the apps would
   *     not contain metric values before this timestamp(Optional query param).
   * @param metricsTimeEnd If specified, returned metrics for the apps would
   *     not contain metric values after this timestamp(Optional query param).
   * @param fromId If specified, retrieve the next set of applications
   *     from the given fromId. The set of applications retrieved is inclusive
   *     of specified fromId. fromId should be taken from the value associated
   *     with FROM_ID info key in entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing
   *     a set of <cite>TimelineEntity</cite> instances representing apps is
   *     returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/users/{userid}/flows/{flowname}/apps/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getFlowApps(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("userid") String userId,
      @PathParam("flowname") String flowName,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {
    return getEntities(req, res, null, null,
        TimelineEntityType.YARN_APPLICATION.toString(), userId, flowName,
        null, limit, createdTimeStart, createdTimeEnd, relatesTo, isRelatedTo,
        infofilters, conffilters, metricfilters, eventfilters,
        confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd, fromId, false);
  }

  /**
   * Return a list of apps for a given user, cluster id and flow name. If number
   * of matching apps are more than the limit, most recent apps till the limit
   * is reached, will be returned.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the apps to be queried belong to
   *     (Mandatory path param).
   * @param userId User id which should match for the apps(Mandatory path param)
   * @param flowName Flow name which should match for the apps(Mandatory path
   *     param).
   * @param limit If specified, defines the number of apps to return. The
   *     maximum possible value for limit can be {@link Long#MAX_VALUE}. If it
   *     is not specified or has a value less than 0, then limit will be
   *     considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched apps should not be created
   *     before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched apps should not be created
   *     after this timestamp(Optional query param).
   * @param relatesTo If specified, matched apps should relate to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param isRelatedTo If specified, matched apps should be related to given
   *     entities associated with a entity type. relatesto is a comma separated
   *     list in the format [entitytype]:[entityid1]:[entityid2]... (Optional
   *     query param).
   * @param infofilters If specified, matched apps should have exact matches
   *     to the given info represented as key-value pairs. This is represented
   *     as infofilters=info1:value1,info2:value2... (Optional query param).
   * @param conffilters If specified, matched apps should have exact matches
   *     to the given configs represented as key-value pairs. This is
   *     represented as conffilters=conf1:value1,conf2:value2... (Optional query
   *     param).
   * @param metricfilters If specified, matched apps should contain the given
   *     metrics. This is represented as metricfilters=metricid1, metricid2...
   *     (Optional query param).
   * @param eventfilters If specified, matched apps should contain the given
   *     events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *     retrieve and send back in response. These configs will be retrieved
   *     irrespective of whether configs are specified in fields to retrieve or
   *     not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *     and send back in response. These metrics will be retrieved
   *     irrespective of whether metrics are specified in fields to retrieve or
   *     not.
   * @param fields Specifies which fields of the app entity object to retrieve,
   *     see {@link Field}. All fields will be retrieved if fields=ALL. If not
   *     specified, 3 fields i.e. entity type(equivalent to YARN_APPLICATION),
   *     app id and app created time is returned(Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *     Considered only if fields contains METRICS/ALL or metricsToRetrieve is
   *     specified. Ignored otherwise. The maximum possible value for
   *     metricsLimit can be {@link Integer#MAX_VALUE}. If it is not specified
   *     or has a value less than 1, and metrics have to be retrieved, then
   *     metricsLimit will be considered as 1 i.e. latest single value of
   *     metric(s) will be returned. (Optional query param).
   * @param metricsTimeStart If specified, returned metrics for the apps would
   *     not contain metric values before this timestamp(Optional query param).
   * @param metricsTimeEnd If specified, returned metrics for the apps would
   *     not contain metric values after this timestamp(Optional query param).
   * @param fromId If specified, retrieve the next set of applications
   *     from the given fromId. The set of applications retrieved is inclusive
   *     of specified fromId. fromId should be taken from the value associated
   *     with FROM_ID info key in entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing
   *     a set of <cite>TimelineEntity</cite> instances representing apps is
   *     returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/users/{userid}/flows/{flowname}/apps/")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getFlowApps(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("userid") String userId,
      @PathParam("flowname") String flowName,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {
    return getEntities(req, res, clusterId, null,
        TimelineEntityType.YARN_APPLICATION.toString(), userId, flowName,
        null, limit, createdTimeStart, createdTimeEnd, relatesTo, isRelatedTo,
        infofilters, conffilters, metricfilters, eventfilters,
        confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd, fromId, false);
  }

  /**
   * Return a set of application-attempt entities for a given applicationId.
   * Cluster ID is not provided by client so default cluster ID has to be taken.
   * If userid, flow name and flowrun id which are optional query parameters are
   * not specified, they will be queried based on app id and default cluster id
   * from the flow context information stored in underlying storage
   * implementation. If number of matching entities are more than the limit,
   * most recent entities till the limit is reached, will be returned.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param appId Application id to which the entities to be queried belong to(
   *          Mandatory path param).
   * @param userId User id which should match for the entities(Optional query
   *          param)
   * @param flowName Flow name which should match for the entities(Optional
   *          query param).
   * @param flowRunId Run id which should match for the entities(Optional query
   *          param).
   * @param limit If specified, defines the number of entities to return. The
   *          maximum possible value for limit can be {@link Long#MAX_VALUE}. If
   *          it is not specified or has a value less than 0, then limit will be
   *          considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched entities should not be
   *          created before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched entities should not be created
   *          after this timestamp(Optional query param).
   * @param relatesTo If specified, matched entities should relate to given
   *          entities associated with a entity type. relatesto is a comma
   *          separated list in the format
   *          [entitytype]:[entityid1]:[entityid2]... (Optional query param).
   * @param isRelatedTo If specified, matched entities should be related to
   *          given entities associated with a entity type. relatesto is a comma
   *          separated list in the format
   *          [entitytype]:[entityid1]:[entityid2]... (Optional query param).
   * @param infofilters If specified, matched entities should have exact matches
   *          to the given info represented as key-value pairs. This is
   *          represented as infofilters=info1:value1,info2:value2... (Optional
   *          query param).
   * @param conffilters If specified, matched entities should have exact matches
   *          to the given configs represented as key-value pairs. This is
   *          represented as conffilters=conf1:value1,conf2:value2... (Optional
   *          query param).
   * @param metricfilters If specified, matched entities should contain the
   *          given metrics. This is represented as metricfilters=metricid1,
   *          metricid2... (Optional query param).
   * @param eventfilters If specified, matched entities should contain the given
   *          events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *          retrieve and send back in response. These configs will be
   *          retrieved irrespective of whether configs are specified in fields
   *          to retrieve or not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *          and send back in response. These metrics will be retrieved
   *          irrespective of whether metrics are specified in fields to
   *          retrieve or not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *          {@link Field}. All fields will be retrieved if fields=ALL. If not
   *          specified, 3 fields i.e. entity type, id, created time is returned
   *          (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *          Considered only if fields contains METRICS/ALL or
   *          metricsToRetrieve is specified. Ignored otherwise. The maximum
   *          possible value for metricsLimit can be {@link Integer#MAX_VALUE}.
   *          If it is not specified or has a value less than 1, and metrics
   *          have to be retrieved, then metricsLimit will be considered as 1
   *          i.e. latest single value of metric(s) will be returned. (Optional
   *          query param).
   * @param metricsTimeStart If specified, returned metrics for the app attempts
   *          would not contain metric values before this timestamp(Optional
   *          query param).
   * @param metricsTimeEnd If specified, returned metrics for the app attempts
   *          would not contain metric values after this timestamp(Optional
   *          query param).
   * @param fromId If specified, retrieve the next set of application-attempt
   *         entities from the given fromId. The set of application-attempt
   *         entities retrieved is inclusive of specified fromId. fromId should
   *         be taken from the value associated with FROM_ID info key in
   *         entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *         set of <cite>TimelineEntity</cite> instances of the app-attempt
   *         entity type is returned.<br>
   *         On failures,<br>
   *         If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *         returned.<br>
   *         If flow context information cannot be retrieved, HTTP 404(Not
   *         Found) is returned.<br>
   *         For all other errors while retrieving data, HTTP 500(Internal
   *         Server Error) is returned.
   */
  @GET
  @Path("/apps/{appid}/appattempts")
  @Produces(MediaType.APPLICATION_JSON)
  public Set<TimelineEntity> getAppAttempts(@Context HttpServletRequest req,
      @Context HttpServletResponse res, @PathParam("appid") String appId,
      @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {

    return getAppAttempts(req, res, null, appId, userId, flowName, flowRunId,
        limit, createdTimeStart, createdTimeEnd, relatesTo, isRelatedTo,
        infofilters, conffilters, metricfilters, eventfilters, confsToRetrieve,
        metricsToRetrieve, fields, metricsLimit, metricsTimeStart,
        metricsTimeEnd, fromId);
  }

  /**
   * Return a set of application-attempt entities for a given applicationId. If
   * userid, flow name and flowrun id which are optional query parameters are
   * not specified, they will be queried based on app id and cluster id from the
   * flow context information stored in underlying storage implementation. If
   * number of matching entities are more than the limit, most recent entities
   * till the limit is reached, will be returned.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the entities to be queried belong to(
   *          Mandatory path param).
   * @param appId Application id to which the entities to be queried belong to(
   *          Mandatory path param).
   * @param userId User id which should match for the entities(Optional query
   *          param)
   * @param flowName Flow name which should match for the entities(Optional
   *          query param).
   * @param flowRunId Run id which should match for the entities(Optional query
   *          param).
   * @param limit If specified, defines the number of entities to return. The
   *          maximum possible value for limit can be {@link Long#MAX_VALUE}. If
   *          it is not specified or has a value less than 0, then limit will be
   *          considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched entities should not be
   *          created before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched entities should not be created
   *          after this timestamp(Optional query param).
   * @param relatesTo If specified, matched entities should relate to given
   *          entities associated with a entity type. relatesto is a comma
   *          separated list in the format
   *          [entitytype]:[entityid1]:[entityid2]... (Optional query param).
   * @param isRelatedTo If specified, matched entities should be related to
   *          given entities associated with a entity type. relatesto is a comma
   *          separated list in the format
   *          [entitytype]:[entityid1]:[entityid2]... (Optional query param).
   * @param infofilters If specified, matched entities should have exact matches
   *          to the given info represented as key-value pairs. This is
   *          represented as infofilters=info1:value1,info2:value2... (Optional
   *          query param).
   * @param conffilters If specified, matched entities should have exact matches
   *          to the given configs represented as key-value pairs. This is
   *          represented as conffilters=conf1:value1,conf2:value2... (Optional
   *          query param).
   * @param metricfilters If specified, matched entities should contain the
   *          given metrics. This is represented as metricfilters=metricid1,
   *          metricid2... (Optional query param).
   * @param eventfilters If specified, matched entities should contain the given
   *          events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *          retrieve and send back in response. These configs will be
   *          retrieved irrespective of whether configs are specified in fields
   *          to retrieve or not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *          and send back in response. These metrics will be retrieved
   *          irrespective of whether metrics are specified in fields to
   *          retrieve or not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *          {@link Field}. All fields will be retrieved if fields=ALL. If not
   *          specified, 3 fields i.e. entity type, id, created time is returned
   *          (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *          Considered only if fields contains METRICS/ALL or
   *          metricsToRetrieve is specified. Ignored otherwise. The maximum
   *          possible value for metricsLimit can be {@link Integer#MAX_VALUE}.
   *          If it is not specified or has a value less than 1, and metrics
   *          have to be retrieved, then metricsLimit will be considered as 1
   *          i.e. latest single value of metric(s) will be returned. (Optional
   *          query param).
   * @param metricsTimeStart If specified, returned metrics for the app attempts
   *          would not contain metric values before this timestamp(Optional
   *          query param).
   * @param metricsTimeEnd If specified, returned metrics for the app attempts
   *          would not contain metric values after this timestamp(Optional
   *          query param).
   * @param fromId If specified, retrieve the next set of application-attempt
   *         entities from the given fromId. The set of application-attempt
   *         entities retrieved is inclusive of specified fromId. fromId should
   *         be taken from the value associated with FROM_ID info key in
   *         entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *         set of <cite>TimelineEntity</cite> instances of the app-attempts
   *         entity type is returned.<br>
   *         On failures,<br>
   *         If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *         returned.<br>
   *         If flow context information cannot be retrieved, HTTP 404(Not
   *         Found) is returned.<br>
   *         For all other errors while retrieving data, HTTP 500(Internal
   *         Server Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/apps/{appid}/appattempts")
  @Produces(MediaType.APPLICATION_JSON)
  public Set<TimelineEntity> getAppAttempts(@Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("appid") String appId, @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {

    return getEntities(req, res, clusterId, appId,
        TimelineEntityType.YARN_APPLICATION_ATTEMPT.toString(), userId,
        flowName, flowRunId, limit, createdTimeStart, createdTimeEnd, relatesTo,
        isRelatedTo, infofilters, conffilters, metricfilters, eventfilters,
        confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd, fromId);
  }

  /**
   * Return a single application-attempt entity for the given attempt Id.
   * Cluster ID is not provided by client so default cluster ID has to be taken.
   * If userid, flow name and flowrun id which are optional query parameters are
   * not specified, they will be queried based on app id and default cluster id
   * from the flow context information stored in underlying storage
   * implementation.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param appId Application id to which the entity to be queried belongs to(
   *          Mandatory path param).
   * @param appAttemptId Application Attempt Id to which the containers belong
   *          to( Mandatory path param).
   * @param userId User id which should match for the entity(Optional query
   *          param).
   * @param flowName Flow name which should match for the entity(Optional query
   *          param).
   * @param flowRunId Run id which should match for the entity(Optional query
   *          param).
   * @param confsToRetrieve If specified, defines which configurations to
   *          retrieve and send back in response. These configs will be
   *          retrieved irrespective of whether configs are specified in fields
   *          to retrieve or not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *          and send back in response. These metrics will be retrieved
   *          irrespective of whether metrics are specified in fields to
   *          retrieve or not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *          {@link Field}. All fields will be retrieved if fields=ALL. If not
   *          specified, 3 fields i.e. entity type, id, created time is returned
   *          (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *          Considered only if fields contains METRICS/ALL or
   *          metricsToRetrieve is specified. Ignored otherwise. The maximum
   *          possible value for metricsLimit can be {@link Integer#MAX_VALUE}.
   *          If it is not specified or has a value less than 1, and metrics
   *          have to be retrieved, then metricsLimit will be considered as 1
   *          i.e. latest single value of metric(s) will be returned. (Optional
   *          query param).
   * @param metricsTimeStart If specified, returned metrics for the app attempt
   *          would not contain metric values before this timestamp(Optional
   *          query param).
   * @param metricsTimeEnd If specified, returned metrics for the app attempt
   *          would not contain metric values after this timestamp(Optional
   *          query param).
   * @param entityIdPrefix Defines the id prefix for the entity to be fetched.
   *          If specified, then entity retrieval will be faster.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *         <cite>TimelineEntity</cite> instance is returned.<br>
   *         On failures,<br>
   *         If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *         returned.<br>
   *         If flow context information cannot be retrieved or entity for the
   *         given entity id cannot be found, HTTP 404(Not Found) is
   *         returned.<br>
   *         For all other errors while retrieving data, HTTP 500(Internal
   *         Server Error) is returned.
   */
  @GET
  @Path("/apps/{appid}/appattempts/{appattemptid}")
  @Produces(MediaType.APPLICATION_JSON)
  public TimelineEntity getAppAttempt(@Context HttpServletRequest req,
      @Context HttpServletResponse res, @PathParam("appid") String appId,
      @PathParam("appattemptid") String appAttemptId,
      @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("entityidprefix") String entityIdPrefix) {
    return getAppAttempt(req, res, null, appId, appAttemptId, userId, flowName,
        flowRunId, confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd, entityIdPrefix);
  }

  /**
   * Return a single application attempt entity of the given entity Id. If
   * userid, flowname and flowrun id which are optional query parameters are not
   * specified, they will be queried based on app id and cluster id from the
   * flow context information stored in underlying storage implementation.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the entity to be queried belongs to(
   *          Mandatory path param).
   * @param appId Application id to which the entity to be queried belongs to(
   *          Mandatory path param).
   * @param appAttemptId Application Attempt Id to which the containers belong
   *          to( Mandatory path param).
   * @param userId User id which should match for the entity(Optional query
   *          param).
   * @param flowName Flow name which should match for the entity(Optional query
   *          param).
   * @param flowRunId Run id which should match for the entity(Optional query
   *          param).
   * @param confsToRetrieve If specified, defines which configurations to
   *          retrieve and send back in response. These configs will be
   *          retrieved irrespective of whether configs are specified in fields
   *          to retrieve or not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *          and send back in response. These metrics will be retrieved
   *          irrespective of whether metrics are specified in fields to
   *          retrieve or not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *          {@link Field}. All fields will be retrieved if fields=ALL. If not
   *          specified, 3 fields i.e. entity type, id and created time is
   *          returned (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *          Considered only if fields contains METRICS/ALL or
   *          metricsToRetrieve is specified. Ignored otherwise. The maximum
   *          possible value for metricsLimit can be {@link Integer#MAX_VALUE}.
   *          If it is not specified or has a value less than 1, and metrics
   *          have to be retrieved, then metricsLimit will be considered as 1
   *          i.e. latest single value of metric(s) will be returned. (Optional
   *          query param).
   * @param metricsTimeStart If specified, returned metrics for the app attempt
   *          would not contain metric values before this timestamp(Optional
   *          query param).
   * @param metricsTimeEnd If specified, returned metrics for the app attempt
   *          would not contain metric values after this timestamp(Optional
   *          query param).
   * @param entityIdPrefix Defines the id prefix for the entity to be fetched.
   *          If specified, then entity retrieval will be faster.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *         <cite>TimelineEntity</cite> instance is returned.<br>
   *         On failures,<br>
   *         If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *         returned.<br>
   *         If flow context information cannot be retrieved or entity for the
   *         given entity id cannot be found, HTTP 404(Not Found) is
   *         returned.<br>
   *         For all other errors while retrieving data, HTTP 500(Internal
   *         Server Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/apps/{appid}/appattempts/{appattemptid}")
  @Produces(MediaType.APPLICATION_JSON)
  public TimelineEntity getAppAttempt(@Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("appid") String appId,
      @PathParam("appattemptid") String appAttemptId,
      @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("entityidprefix") String entityIdPrefix) {
    return getEntity(req, res, clusterId, appId,
        TimelineEntityType.YARN_APPLICATION_ATTEMPT.toString(), appAttemptId,
        userId, flowName, flowRunId, confsToRetrieve, metricsToRetrieve, fields,
        metricsLimit, metricsTimeStart, metricsTimeEnd, entityIdPrefix);
  }

  /**
   * Return a set of container entities belonging to given application attempt
   * id. Cluster ID is not provided by client so default cluster ID has to be
   * taken. If userid, flow name and flowrun id which are optional query
   * parameters are not specified, they will be queried based on app id and
   * default cluster id from the flow context information stored in underlying
   * storage implementation. If number of matching entities are more than the
   * limit, most recent entities till the limit is reached, will be returned.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param appId Application id to which the entities to be queried belong to(
   *          Mandatory path param).
   * @param appattemptId Application Attempt Id to which the containers belong
   *          to( Mandatory path param).
   * @param userId User id which should match for the entities(Optional query
   *          param)
   * @param flowName Flow name which should match for the entities(Optional
   *          query param).
   * @param flowRunId Run id which should match for the entities(Optional query
   *          param).
   * @param limit If specified, defines the number of entities to return. The
   *          maximum possible value for limit can be {@link Long#MAX_VALUE}. If
   *          it is not specified or has a value less than 0, then limit will be
   *          considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched entities should not be
   *          created before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched entities should not be created
   *          after this timestamp(Optional query param).
   * @param relatesTo If specified, matched entities should relate to given
   *          entities associated with a entity type. relatesto is a comma
   *          separated list in the format
   *          [entitytype]:[entityid1]:[entityid2]... (Optional query param).
   * @param isRelatedTo If specified, matched entities should be related to
   *          given entities associated with a entity type. relatesto is a comma
   *          separated list in the format
   *          [entitytype]:[entityid1]:[entityid2]... (Optional query param).
   * @param infofilters If specified, matched entities should have exact matches
   *          to the given info represented as key-value pairs. This is
   *          represented as infofilters=info1:value1,info2:value2... (Optional
   *          query param).
   * @param conffilters If specified, matched entities should have exact matches
   *          to the given configs represented as key-value pairs. This is
   *          represented as conffilters=conf1:value1,conf2:value2... (Optional
   *          query param).
   * @param metricfilters If specified, matched entities should contain the
   *          given metrics. This is represented as metricfilters=metricid1,
   *          metricid2... (Optional query param).
   * @param eventfilters If specified, matched entities should contain the given
   *          events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *          retrieve and send back in response. These configs will be
   *          retrieved irrespective of whether configs are specified in fields
   *          to retrieve or not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *          and send back in response. These metrics will be retrieved
   *          irrespective of whether metrics are specified in fields to
   *          retrieve or not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *          {@link Field}. All fields will be retrieved if fields=ALL. If not
   *          specified, 3 fields i.e. entity type, id, created time is returned
   *          (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *          Considered only if fields contains METRICS/ALL or
   *          metricsToRetrieve is specified. Ignored otherwise. The maximum
   *          possible value for metricsLimit can be {@link Integer#MAX_VALUE}.
   *          If it is not specified or has a value less than 1, and metrics
   *          have to be retrieved, then metricsLimit will be considered as 1
   *          i.e. latest single value of metric(s) will be returned. (Optional
   *          query param).
   * @param metricsTimeStart If specified, returned metrics for the containers
   *          would not contain metric values before this timestamp(Optional
   *          query param).
   * @param metricsTimeEnd If specified, returned metrics for the containers
   *          would not contain metric values after this timestamp(Optional
   *          query param).
   * @param fromId If specified, retrieve the next set of container
   *         entities from the given fromId. The set of container
   *         entities retrieved is inclusive of specified fromId. fromId should
   *         be taken from the value associated with FROM_ID info key in
   *         entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *         set of <cite>TimelineEntity</cite> instances of the containers
   *         belongs to given application attempt id.<br>
   *         On failures,<br>
   *         If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *         returned.<br>
   *         If flow context information cannot be retrieved, HTTP 404(Not
   *         Found) is returned.<br>
   *         For all other errors while retrieving data, HTTP 500(Internal
   *         Server Error) is returned.
   */
  @GET
  @Path("/apps/{appid}/appattempts/{appattemptid}/containers")
  @Produces(MediaType.APPLICATION_JSON)
  public Set<TimelineEntity> getContainers(@Context HttpServletRequest req,
      @Context HttpServletResponse res, @PathParam("appid") String appId,
      @PathParam("appattemptid") String appattemptId,
      @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {
    return getContainers(req, res, null, appId, appattemptId, userId, flowName,
        flowRunId, limit, createdTimeStart, createdTimeEnd, relatesTo,
        isRelatedTo, infofilters, conffilters, metricfilters, eventfilters,
        confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd, fromId);
  }

  /**
   * Return a set of container entities belonging to given application attempt
   * id. If userid, flow name and flowrun id which are optional query parameters
   * are not specified, they will be queried based on app id and cluster id from
   * the flow context information stored in underlying storage implementation.
   * If number of matching entities are more than the limit, most recent
   * entities till the limit is reached, will be returned.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the entities to be queried belong to(
   *          Mandatory path param).
   * @param appId Application id to which the entities to be queried belong to(
   *          Mandatory path param).
   * @param appattemptId Application Attempt Id to which the containers belong
   *          to( Mandatory path param).
   * @param userId User id which should match for the entities(Optional query
   *          param)
   * @param flowName Flow name which should match for the entities(Optional
   *          query param).
   * @param flowRunId Run id which should match for the entities(Optional query
   *          param).
   * @param limit If specified, defines the number of entities to return. The
   *          maximum possible value for limit can be {@link Long#MAX_VALUE}. If
   *          it is not specified or has a value less than 0, then limit will be
   *          considered as 100. (Optional query param).
   * @param createdTimeStart If specified, matched entities should not be
   *          created before this timestamp(Optional query param).
   * @param createdTimeEnd If specified, matched entities should not be created
   *          after this timestamp(Optional query param).
   * @param relatesTo If specified, matched entities should relate to given
   *          entities associated with a entity type. relatesto is a comma
   *          separated list in the format
   *          [entitytype]:[entityid1]:[entityid2]... (Optional query param).
   * @param isRelatedTo If specified, matched entities should be related to
   *          given entities associated with a entity type. relatesto is a comma
   *          separated list in the format
   *          [entitytype]:[entityid1]:[entityid2]... (Optional query param).
   * @param infofilters If specified, matched entities should have exact matches
   *          to the given info represented as key-value pairs. This is
   *          represented as infofilters=info1:value1,info2:value2... (Optional
   *          query param).
   * @param conffilters If specified, matched entities should have exact matches
   *          to the given configs represented as key-value pairs. This is
   *          represented as conffilters=conf1:value1,conf2:value2... (Optional
   *          query param).
   * @param metricfilters If specified, matched entities should contain the
   *          given metrics. This is represented as metricfilters=metricid1,
   *          metricid2... (Optional query param).
   * @param eventfilters If specified, matched entities should contain the given
   *          events. This is represented as eventfilters=eventid1, eventid2...
   * @param confsToRetrieve If specified, defines which configurations to
   *          retrieve and send back in response. These configs will be
   *          retrieved irrespective of whether configs are specified in fields
   *          to retrieve or not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *          and send back in response. These metrics will be retrieved
   *          irrespective of whether metrics are specified in fields to
   *          retrieve or not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *          {@link Field}. All fields will be retrieved if fields=ALL. If not
   *          specified, 3 fields i.e. entity type, id, created time is returned
   *          (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *          Considered only if fields contains METRICS/ALL or
   *          metricsToRetrieve is specified. Ignored otherwise. The maximum
   *          possible value for metricsLimit can be {@link Integer#MAX_VALUE}.
   *          If it is not specified or has a value less than 1, and metrics
   *          have to be retrieved, then metricsLimit will be considered as 1
   *          i.e. latest single value of metric(s) will be returned. (Optional
   *          query param).
   * @param metricsTimeStart If specified, returned metrics for the containers
   *          would not contain metric values before this timestamp(Optional
   *          query param).
   * @param metricsTimeEnd If specified, returned metrics for the containers
   *          would not contain metric values after this timestamp(Optional
   *          query param).
   * @param fromId If specified, retrieve the next set of container
   *         entities from the given fromId. The set of container
   *         entities retrieved is inclusive of specified fromId. fromId should
   *         be taken from the value associated with FROM_ID info key in
   *         entity response which was sent earlier.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *         set of <cite>TimelineEntity</cite> instances of the containers
   *         belongs to given application attempt id.<br>
   *         On failures,<br>
   *         If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *         returned.<br>
   *         If flow context information cannot be retrieved, HTTP 404(Not
   *         Found) is returned.<br>
   *         For all other errors while retrieving data, HTTP 500(Internal
   *         Server Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/apps/{appid}/appattempts/{appattemptid}/containers")
  @Produces(MediaType.APPLICATION_JSON)
  public Set<TimelineEntity> getContainers(@Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("appid") String appId,
      @PathParam("appattemptid") String appattemptId,
      @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {

    String entityType = TimelineEntityType.YARN_CONTAINER.toString();
    String parentEntityType =
        TimelineEntityType.YARN_APPLICATION_ATTEMPT.toString();
    String jsonFormatString = "{\"type\":\"" + parentEntityType + "\",\"id\":\""
        + appattemptId + "\"}";
    String containerFilters =
        "SYSTEM_INFO_PARENT_ENTITY eq " + jsonFormatString;
    String infofilter;
    if (infofilters != null) {
      infofilter = containerFilters + " AND " + infofilters;
    } else {
      infofilter = containerFilters;
    }
    return getEntities(req, res, clusterId, appId, entityType, userId, flowName,
        flowRunId, limit, createdTimeStart, createdTimeEnd, relatesTo,
        isRelatedTo, infofilter, conffilters, metricfilters, eventfilters,
        confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd, fromId);
  }

  /**
   * Return a single container entity for the given container Id. Cluster ID is
   * not provided by client so default cluster ID has to be taken. If userid,
   * flow name and flowrun id which are optional query parameters are not
   * specified, they will be queried based on app id and default cluster id from
   * the flow context information stored in underlying storage implementation.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param appId Application id to which the entity to be queried belongs to(
   *          Mandatory path param).
   * @param containerId Container Id to which the entity to be queried belongs
   *          to( Mandatory path param).
   * @param userId User id which should match for the entity(Optional query
   *          param).
   * @param flowName Flow name which should match for the entity(Optional query
   *          param).
   * @param flowRunId Run id which should match for the entity(Optional query
   *          param).
   * @param confsToRetrieve If specified, defines which configurations to
   *          retrieve and send back in response. These configs will be
   *          retrieved irrespective of whether configs are specified in fields
   *          to retrieve or not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *          and send back in response. These metrics will be retrieved
   *          irrespective of whether metrics are specified in fields to
   *          retrieve or not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *          {@link Field}. All fields will be retrieved if fields=ALL. If not
   *          specified, 3 fields i.e. entity type, id, created time is returned
   *          (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *          Considered only if fields contains METRICS/ALL or
   *          metricsToRetrieve is specified. Ignored otherwise. The maximum
   *          possible value for metricsLimit can be {@link Integer#MAX_VALUE}.
   *          If it is not specified or has a value less than 1, and metrics
   *          have to be retrieved, then metricsLimit will be considered as 1
   *          i.e. latest single value of metric(s) will be returned. (Optional
   *          query param).
   * @param metricsTimeStart If specified, returned metrics for the container
   *          would not contain metric values before this timestamp(Optional
   *          query param).
   * @param metricsTimeEnd If specified, returned metrics for the container
   *          would not contain metric values after this timestamp(Optional
   *          query param).
   * @param entityIdPrefix Defines the id prefix for the entity to be fetched.
   *          If specified, then entity retrieval will be faster.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing
   *         <cite>TimelineEntity</cite> instance is returned.<br>
   *         On failures,<br>
   *         If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *         returned.<br>
   *         If flow context information cannot be retrieved or entity for the
   *         given entity id cannot be found, HTTP 404(Not Found) is
   *         returned.<br>
   *         For all other errors while retrieving data, HTTP 500(Internal
   *         Server Error) is returned.
   */
  @GET
  @Path("/apps/{appid}/containers/{containerid}")
  @Produces(MediaType.APPLICATION_JSON)
  public TimelineEntity getContainer(@Context HttpServletRequest req,
      @Context HttpServletResponse res, @PathParam("appid") String appId,
      @PathParam("containerid") String containerId,
      @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("entityidprefix") String entityIdPrefix) {
    return getContainer(req, res, null, appId, containerId, userId, flowName,
        flowRunId, confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        entityIdPrefix, metricsTimeStart, metricsTimeEnd);
  }

  /**
   * Return a single container entity for the given container Id. If userid,
   * flowname and flowrun id which are optional query parameters are not
   * specified, they will be queried based on app id and cluster id from the
   * flow context information stored in underlying storage implementation.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the entity to be queried belongs to(
   *          Mandatory path param).
   * @param appId Application id to which the entity to be queried belongs to(
   *          Mandatory path param).
   * @param containerId Container Id to which the entity to be queried belongs
   *          to( Mandatory path param).
   * @param userId User id which should match for the entity(Optional query
   *          param).
   * @param flowName Flow name which should match for the entity(Optional query
   *          param).
   * @param flowRunId Run id which should match for the entity(Optional query
   *          param).
   * @param confsToRetrieve If specified, defines which configurations to
   *          retrieve and send back in response. These configs will be
   *          retrieved irrespective of whether configs are specified in fields
   *          to retrieve or not.
   * @param metricsToRetrieve If specified, defines which metrics to retrieve
   *          and send back in response. These metrics will be retrieved
   *          irrespective of whether metrics are specified in fields to
   *          retrieve or not.
   * @param fields Specifies which fields of the entity object to retrieve, see
   *          {@link Field}. All fields will be retrieved if fields=ALL. If not
   *          specified, 3 fields i.e. entity type, id and created time is
   *          returned (Optional query param).
   * @param metricsLimit If specified, defines the number of metrics to return.
   *          Considered only if fields contains METRICS/ALL or
   *          metricsToRetrieve is specified. Ignored otherwise. The maximum
   *          possible value for metricsLimit can be {@link Integer#MAX_VALUE}.
   *          If it is not specified or has a value less than 1, and metrics
   *          have to be retrieved, then metricsLimit will be considered as 1
   *          i.e. latest single value of metric(s) will be returned. (Optional
   *          query param).
   * @param metricsTimeStart If specified, returned metrics for the container
   *          would not contain metric values before this timestamp(Optional
   *          query param).
   * @param metricsTimeEnd If specified, returned metrics for the container
   *          would not contain metric values after this timestamp(Optional
   *          query param).
   * @param entityIdPrefix Defines the id prefix for the entity to be fetched.
   *          If specified, then entity retrieval will be faster.
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *         <cite>TimelineEntity</cite> instance is returned.<br>
   *         On failures,<br>
   *         If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *         returned.<br>
   *         If flow context information cannot be retrieved or entity for the
   *         given entity id cannot be found, HTTP 404(Not Found) is
   *         returned.<br>
   *         For all other errors while retrieving data, HTTP 500(Internal
   *         Server Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/apps/{appid}/containers/{containerid}")
  @Produces(MediaType.APPLICATION_JSON)
  public TimelineEntity getContainer(@Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("appid") String appId,
      @PathParam("containerid") String containerId,
      @QueryParam("userid") String userId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("entityidprefix") String entityIdPrefix) {
    return getEntity(req, res, clusterId, appId,
        TimelineEntityType.YARN_CONTAINER.toString(), containerId, userId,
        flowName, flowRunId, confsToRetrieve, metricsToRetrieve, fields,
        metricsLimit, metricsTimeStart, metricsTimeEnd, entityIdPrefix);
  }

  /**
   * Returns a set of available entity types for a given app id. Cluster ID is
   * not provided by client so default cluster ID has to be taken. If userid,
   * flow name and flow run id which are optional query parameters are not
   * specified, they will be queried based on app id and cluster id from the
   * flow context information stored in underlying storage implementation.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param appId Application id to be queried(Mandatory path param).
   * @param flowName Flow name which should match for the app(Optional query
   *     param).
   * @param flowRunId Run id which should match for the app(Optional query
   *     param).
   * @param userId User id which should match for the app(Optional query param).
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     list contains all timeline entity types is returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     If flow context information cannot be retrieved or app for the given
   *     app id cannot be found, HTTP 404(Not Found) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/apps/{appid}/entity-types")
  @Produces(MediaType.APPLICATION_JSON)
  public Set<String> getEntityTypes(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("appid") String appId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("userid") String userId) {
    return getEntityTypes(req, res, null, appId, flowName, flowRunId, userId);
  }

  /**
   * Returns a set of available entity types for a given app id. If userid,
   * flow name and flow run id which are optional query parameters are not
   * specified, they will be queried based on app id and cluster id from the
   * flow context information stored in underlying storage implementation.
   *
   * @param req Servlet request.
   * @param res Servlet response.
   * @param clusterId Cluster id to which the app to be queried belong to(
   *     Mandatory path param).
   * @param appId Application id to be queried(Mandatory path param).
   * @param flowName Flow name which should match for the app(Optional query
   *     param).
   * @param flowRunId Run id which should match for the app(Optional query
   *     param).
   * @param userId User id which should match for the app(Optional query param).
   *
   * @return If successful, a HTTP 200(OK) response having a JSON representing a
   *     list contains all timeline entity types is returned.<br>
   *     On failures,<br>
   *     If any problem occurs in parsing request, HTTP 400(Bad Request) is
   *     returned.<br>
   *     If flow context information cannot be retrieved or app for the given
   *     app id cannot be found, HTTP 404(Not Found) is returned.<br>
   *     For all other errors while retrieving data, HTTP 500(Internal Server
   *     Error) is returned.
   */
  @GET
  @Path("/clusters/{clusterid}/apps/{appid}/entity-types")
  @Produces(MediaType.APPLICATION_JSON)
  public Set<String> getEntityTypes(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("appid") String appId,
      @QueryParam("flowname") String flowName,
      @QueryParam("flowrunid") String flowRunId,
      @QueryParam("userid") String userId) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    Set<String> results = null;
    try {
      TimelineReaderContext context = TimelineReaderWebServicesUtils.
          createTimelineReaderContext(clusterId, userId, flowName, flowRunId,
          appId, null, null, null);
      results = timelineReaderManager.getEntityTypes(context);
      checkAccess(getTimelineReaderManager(), callerUGI, context.getUserId());
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime, "flowrunid");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntityTypesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    return results;
  }

  @GET
  @Path("/users/{userid}/entities/{entitytype}")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getSubAppEntities(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("userid") String userId,
      @PathParam("entitytype") String entityType,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {
    return getSubAppEntities(req, res, null, userId, entityType, limit,
        createdTimeStart, createdTimeEnd, relatesTo, isRelatedTo, infofilters,
        conffilters, metricfilters, eventfilters, confsToRetrieve,
        metricsToRetrieve, fields, metricsLimit, metricsTimeStart,
        metricsTimeEnd, fromId);
  }

  @GET
  @Path("/clusters/{clusterid}/users/{userid}/entities/{entitytype}")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getSubAppEntities(
      @Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("userid") String userId,
      @PathParam("entitytype") String entityType,
      @QueryParam("limit") String limit,
      @QueryParam("createdtimestart") String createdTimeStart,
      @QueryParam("createdtimeend") String createdTimeEnd,
      @QueryParam("relatesto") String relatesTo,
      @QueryParam("isrelatedto") String isRelatedTo,
      @QueryParam("infofilters") String infofilters,
      @QueryParam("conffilters") String conffilters,
      @QueryParam("metricfilters") String metricfilters,
      @QueryParam("eventfilters") String eventfilters,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("fromid") String fromId) {
    String url = req.getRequestURI() +
        (req.getQueryString() == null ? "" :
            QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    Set<TimelineEntity> entities = null;
    try {
      TimelineReaderContext context =
          TimelineReaderWebServicesUtils.createTimelineReaderContext(clusterId,
              null, null, null, null, entityType, null, null, userId);
      entities = timelineReaderManager.getEntities(context,
          TimelineReaderWebServicesUtils.createTimelineEntityFilters(
          limit, createdTimeStart, createdTimeEnd, relatesTo, isRelatedTo,
              infofilters, conffilters, metricfilters, eventfilters,
              fromId),
          TimelineReaderWebServicesUtils.createTimelineDataToRetrieve(
          confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
          metricsTimeStart, metricsTimeEnd));
      checkAccessForSubAppEntities(entities,callerUGI);
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime,
          "Either limit or createdtime start/end or metricslimit or metricstime"
              + " start/end or fromid");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info("Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entities == null) {
      entities = Collections.emptySet();
    }
    return entities;
  }

  @GET
  @Path("/users/{userid}/entities/{entitytype}/{entityid}")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getSubAppEntities(@Context HttpServletRequest req,
      @Context HttpServletResponse res, @PathParam("userid") String userId,
      @PathParam("entitytype") String entityType,
      @PathParam("entityid") String entityId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("entityidprefix") String entityIdPrefix) {
    return getSubAppEntities(req, res, null, userId, entityType, entityId,
        confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
        metricsTimeStart, metricsTimeEnd, entityIdPrefix);
  }

  @GET
  @Path("/clusters/{clusterid}/users/{userid}/entities/{entitytype}/{entityid}")
  @Produces(MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8)
  public Set<TimelineEntity> getSubAppEntities(@Context HttpServletRequest req,
      @Context HttpServletResponse res,
      @PathParam("clusterid") String clusterId,
      @PathParam("userid") String userId,
      @PathParam("entitytype") String entityType,
      @PathParam("entityid") String entityId,
      @QueryParam("confstoretrieve") String confsToRetrieve,
      @QueryParam("metricstoretrieve") String metricsToRetrieve,
      @QueryParam("fields") String fields,
      @QueryParam("metricslimit") String metricsLimit,
      @QueryParam("metricstimestart") String metricsTimeStart,
      @QueryParam("metricstimeend") String metricsTimeEnd,
      @QueryParam("entityidprefix") String entityIdPrefix) {
    String url = req.getRequestURI() + (req.getQueryString() == null ? ""
        : QUERY_STRING_SEP + req.getQueryString());
    UserGroupInformation callerUGI =
        TimelineReaderWebServicesUtils.getUser(req);
    LOG.info("Received URL {} from user {}",
        url, TimelineReaderWebServicesUtils.getUserName(callerUGI));
    long startTime = Time.monotonicNow();
    boolean succeeded = false;
    init(res);
    TimelineReaderManager timelineReaderManager = getTimelineReaderManager();
    Set<TimelineEntity> entities = null;
    try {
      TimelineReaderContext context = TimelineReaderWebServicesUtils
          .createTimelineReaderContext(clusterId, null, null, null, null,
              entityType, entityIdPrefix, entityId, userId);
      entities = timelineReaderManager.getEntities(context,
          new TimelineEntityFilters.Builder().build(),
          TimelineReaderWebServicesUtils.createTimelineDataToRetrieve(
              confsToRetrieve, metricsToRetrieve, fields, metricsLimit,
              metricsTimeStart, metricsTimeEnd));
      checkAccessForSubAppEntities(entities,callerUGI);
      succeeded = true;
    } catch (Exception e) {
      handleException(e, url, startTime, "Either metricslimit or metricstime"
          + " start/end");
    } finally {
      long latency = Time.monotonicNow() - startTime;
      METRICS.addGetEntitiesLatency(latency, succeeded);
      LOG.info(
          "Processed URL {} (Took {} ms.)", url, latency);
    }
    if (entities == null) {
      entities = Collections.emptySet();
    }

    return entities;
  }

  static boolean isDisplayEntityPerUserFilterEnabled(Configuration config) {
    return !config
        .getBoolean(YarnConfiguration.TIMELINE_SERVICE_READ_AUTH_ENABLED,
            YarnConfiguration.DEFAULT_TIMELINE_SERVICE_READ_AUTH_ENABLED)
        && config
        .getBoolean(YarnConfiguration.FILTER_ENTITY_LIST_BY_USER, false);
  }

  // TODO to be removed or modified once ACL story is played
  private void checkAccessForSubAppEntities(Set<TimelineEntity> entities,
      UserGroupInformation callerUGI) throws Exception {
    if (entities != null && entities.size() > 0
        && isDisplayEntityPerUserFilterEnabled(
        getTimelineReaderManager().getConfig())) {
      TimelineReaderContext timelineReaderContext = null;
      TimelineEntity entity = entities.iterator().next();
      String fromId =
          (String) entity.getInfo().get(TimelineReaderUtils.FROMID_KEY);
      timelineReaderContext =
          TimelineFromIdConverter.SUB_APPLICATION_ENTITY_FROMID
              .decodeUID(fromId);
      checkAccess(getTimelineReaderManager(), callerUGI,
          timelineReaderContext.getDoAsUser());
    }
  }

  // TODO to be removed or modified once ACL story is played
  private void checkAccessForAppEntity(TimelineEntity entity,
      UserGroupInformation callerUGI) throws Exception {
    if (entity != null && isDisplayEntityPerUserFilterEnabled(
        getTimelineReaderManager().getConfig())) {
      String fromId =
          (String) entity.getInfo().get(TimelineReaderUtils.FROMID_KEY);
      TimelineReaderContext timelineReaderContext =
          TimelineFromIdConverter.APPLICATION_FROMID.decodeUID(fromId);
      checkAccess(getTimelineReaderManager(), callerUGI,
          timelineReaderContext.getUserId());
    }
  }

  // TODO to be removed or modified once ACL story is played
  private void checkAccessForGenericEntity(TimelineEntity entity,
      UserGroupInformation callerUGI) throws Exception {
    if (entity != null && isDisplayEntityPerUserFilterEnabled(
        getTimelineReaderManager().getConfig())) {
      String fromId =
          (String) entity.getInfo().get(TimelineReaderUtils.FROMID_KEY);
      TimelineReaderContext timelineReaderContext =
          TimelineFromIdConverter.GENERIC_ENTITY_FROMID.decodeUID(fromId);
      checkAccess(getTimelineReaderManager(), callerUGI,
          timelineReaderContext.getUserId());
    }
  }

  // TODO to be removed or modified once ACL story is played
  private void checkAccessForGenericEntities(Set<TimelineEntity> entities,
      UserGroupInformation callerUGI, String entityType) throws Exception {
    if (entities != null && entities.size() > 0
        && isDisplayEntityPerUserFilterEnabled(
        getTimelineReaderManager().getConfig())) {
      TimelineReaderContext timelineReaderContext = null;
      TimelineEntity entity = entities.iterator().next();
      String uid =
          (String) entity.getInfo().get(TimelineReaderUtils.FROMID_KEY);
      if (TimelineEntityType.YARN_APPLICATION.matches(entityType)) {
        timelineReaderContext =
            TimelineFromIdConverter.APPLICATION_FROMID.decodeUID(uid);
      } else {
        timelineReaderContext =
            TimelineFromIdConverter.GENERIC_ENTITY_FROMID.decodeUID(uid);
      }
      checkAccess(getTimelineReaderManager(), callerUGI,
          timelineReaderContext.getUserId());
    }
  }

  // TODO to be removed/modified once ACL story has played
  static boolean validateAuthUserWithEntityUser(
      TimelineReaderManager readerManager, UserGroupInformation ugi,
      String entityUser) {
    String authUser = TimelineReaderWebServicesUtils.getUserName(ugi);
    String requestedUser = TimelineReaderWebServicesUtils.parseStr(entityUser);
    LOG.debug(
          "Authenticated User: {} Requested User:{}", authUser, entityUser);
    return (readerManager.checkAccess(ugi) || authUser.equals(requestedUser));
  }

  // TODO to be removed/modified once ACL story has played
  static boolean checkAccess(TimelineReaderManager readerManager,
      UserGroupInformation ugi, String entityUser) {
    if (isDisplayEntityPerUserFilterEnabled(readerManager.getConfig())) {
      if (!validateAuthUserWithEntityUser(readerManager, ugi,
          entityUser)) {
        String userName = ugi == null ? null : ugi.getShortUserName();
        String msg = "User " + userName
            + " is not allowed to read TimelineService V2 data.";
        throw new ForbiddenException(msg);
      }
    }
    return true;
  }

  // TODO to be removed or modified once ACL story is played
  static void checkAccess(TimelineReaderManager readerManager,
      UserGroupInformation callerUGI, Set<TimelineEntity> entities,
      String entityUserKey, boolean verifyForAllEntity) {
    if (entities.size() > 0 && isDisplayEntityPerUserFilterEnabled(
        readerManager.getConfig())) {
      Set<TimelineEntity> userEntities = new LinkedHashSet<>();
      userEntities.addAll(entities);
      for (TimelineEntity entity : userEntities) {
        if (entity.getInfo() != null) {
          String userId = (String) entity.getInfo().get(entityUserKey);
          if (!validateAuthUserWithEntityUser(readerManager, callerUGI,
              userId)) {
            entities.remove(entity);
            if (!verifyForAllEntity) {
              break;
            }
          }
        }
      }
    }
  }
}
