@GET
  @Path(RMWSConsts.APPS)
  @Produces({ MediaType.APPLICATION_JSON + "; " + JettyUtils.UTF_8,
      MediaType.APPLICATION_XML + "; " + JettyUtils.UTF_8 })
  @Override
  public AppsInfo getApps(@Context HttpServletRequest hsr,
      @QueryParam(RMWSConsts.STATE) String stateQuery,
      @QueryParam(RMWSConsts.STATES) Set<String> statesQuery,
      @QueryParam(RMWSConsts.FINAL_STATUS) String finalStatusQuery,
      @QueryParam(RMWSConsts.USER) String userQuery,
      @QueryParam(RMWSConsts.QUEUE) String queueQuery,
      @QueryParam(RMWSConsts.LIMIT) String limit,
      @QueryParam(RMWSConsts.STARTED_TIME_BEGIN) String startedBegin,
      @QueryParam(RMWSConsts.STARTED_TIME_END) String startedEnd,
      @QueryParam(RMWSConsts.FINISHED_TIME_BEGIN) String finishBegin,
      @QueryParam(RMWSConsts.FINISHED_TIME_END) String finishEnd,
      @QueryParam(RMWSConsts.APPLICATION_TYPES) Set<String> applicationTypes,
      @QueryParam(RMWSConsts.APPLICATION_TAGS) Set<String> applicationTags,
      @QueryParam(RMWSConsts.NAME) String name,
      @QueryParam(RMWSConsts.DESELECTS) Set<String> unselectedFields) {

    AppsCacheKey cacheKey = AppsCacheKey.newInstance(stateQuery, new HashSet<>(statesQuery),
        finalStatusQuery, userQuery, queueQuery, limit, startedBegin, startedEnd, finishBegin,
        finishEnd, new HashSet<>(applicationTypes), new HashSet<>(applicationTags), name,
        unselectedFields);
    if (this.enableAppsCache) {
      long successTimes = getAppsSuccessTimes.incrementAndGet();
      if (successTimes % 1000 == 0) {
        LOG.debug("hit cache info: getAppsSuccessTimes={}, hitAppsCacheTimes={}",
            successTimes, hitAppsCacheTimes.get());
      }
      AppsInfo appsInfo = appsLRUCache.get(cacheKey);
      if (appsInfo != null) {
        hitAppsCacheTimes.getAndIncrement();
        return appsInfo;
      }
    }

    initForReadableEndpoints();

    GetApplicationsRequest request =
            ApplicationsRequestBuilder.create()
                    .withStateQuery(stateQuery)
                    .withStatesQuery(statesQuery)
                    .withUserQuery(userQuery)
                    .withQueueQuery(rm, queueQuery)
                    .withLimit(limit)
                    .withStartedTimeBegin(startedBegin)
                    .withStartedTimeEnd(startedEnd)
                    .withFinishTimeBegin(finishBegin)
                    .withFinishTimeEnd(finishEnd)
                    .withApplicationTypes(applicationTypes)
                    .withApplicationTags(applicationTags)
                    .withName(name)
            .build();

    List<ApplicationReport> appReports;
    try {
      appReports = rm.getClientRMService().getApplications(request)
          .getApplicationList();
    } catch (YarnException e) {
      LOG.error("Unable to retrieve apps from ClientRMService", e);
      throw new YarnRuntimeException(
          "Unable to retrieve apps from ClientRMService", e);
    }

    final ConcurrentMap<ApplicationId, RMApp> apps =
        rm.getRMContext().getRMApps();
    AppsInfo allApps = new AppsInfo();
    for (ApplicationReport report : appReports) {
      RMApp rmapp = apps.get(report.getApplicationId());
      if (rmapp == null) {
        continue;
      }

      if (finalStatusQuery != null && !finalStatusQuery.isEmpty()) {
        FinalApplicationStatus.valueOf(finalStatusQuery);
        if (!rmapp.getFinalApplicationStatus().toString()
            .equalsIgnoreCase(finalStatusQuery)) {
          continue;
        }
      }

      DeSelectFields deSelectFields = new DeSelectFields();
      deSelectFields.initFields(unselectedFields);

      boolean allowAccess = hasAccess(rmapp, hsr);
      // Given RM is configured to display apps per user, skip apps to which
      // this caller doesn't have access to view.
      if (filterAppsByUser && !allowAccess) {
        continue;
      }

      AppInfo app = new AppInfo(rm, rmapp, allowAccess,
          WebAppUtils.getHttpSchemePrefix(conf), deSelectFields);
      allApps.add(app);
    }

    if (filterInvalidXMLChars) {
      final String format = hsr.getHeader(HttpHeaders.ACCEPT);
      if (format != null &&
          format.toLowerCase().contains(MediaType.APPLICATION_XML)) {
        for (AppInfo appInfo : allApps.getApps()) {
          appInfo.setNote(escapeInvalidXMLCharacters(appInfo.getNote()));
        }
      }
    }

    if (enableAppsCache) {
      appsLRUCache.put(cacheKey, allApps);
      getAppsSuccessTimes.getAndIncrement();
    }
    return allApps;
  }
@Test
  public void testAppsRace() throws Exception {
    // mock up an RM that returns app reports for apps that don't exist
    // in the RMApps list
    ApplicationId appId = ApplicationId.newInstance(1, 1);
    ApplicationReport mockReport = mock(ApplicationReport.class);
    when(mockReport.getApplicationId()).thenReturn(appId);
    GetApplicationsResponse mockAppsResponse =
        mock(GetApplicationsResponse.class);
    when(mockAppsResponse.getApplicationList())
      .thenReturn(Arrays.asList(new ApplicationReport[] { mockReport }));
    ClientRMService mockClientSvc = mock(ClientRMService.class);
    when(mockClientSvc.getApplications(isA(GetApplicationsRequest.class)))
        .thenReturn(mockAppsResponse);
    ResourceManager mockRM = mock(ResourceManager.class);
    RMContextImpl rmContext = new RMContextImpl(null, null, null, null, null,
        null, null, null, null, null);
    when(mockRM.getRMContext()).thenReturn(rmContext);
    when(mockRM.getClientRMService()).thenReturn(mockClientSvc);
    rmContext.setNodeLabelManager(mock(RMNodeLabelsManager.class));

    RMWebServices webSvc = new RMWebServices(mockRM, new Configuration(),
        mock(HttpServletResponse.class));

    final Set<String> emptySet =
        Collections.unmodifiableSet(Collections.<String>emptySet());

    // verify we don't get any apps when querying
    HttpServletRequest mockHsr = mock(HttpServletRequest.class);
    AppsInfo appsInfo = webSvc.getApps(mockHsr, null, emptySet, null,
        null, null, null, null, null, null, null, emptySet, emptySet, null,
        null);
    assertTrue(appsInfo.getApps().isEmpty());

    // verify we don't get an NPE when specifying a final status query
    appsInfo = webSvc.getApps(mockHsr, null, emptySet, "FAILED",
        null, null, null, null, null, null, null, emptySet, emptySet, null,
        null);
    assertTrue(appsInfo.getApps().isEmpty());
  }