@Override
  public Set<String> validate(String entityType, Set<String> ids) throws IllegalStateException {
    if (!areParametersValid(EntityNames.STUDENT_COMPETENCY, entityType, ids)) {
      return Collections.EMPTY_SET;
    }

    NeutralQuery query =
        new NeutralQuery(
            new NeutralCriteria(ParameterConstants.ID, NeutralCriteria.CRITERIA_IN, ids));
    query.setIncludeFields(Arrays.asList(ParameterConstants.STUDENT_SECTION_ASSOCIATION_ID));
    Iterable<Entity> comps = getRepo().findAll(EntityNames.STUDENT_COMPETENCY, query);
    Map<String, Set<String>> secAssocIds = new HashMap<String, Set<String>>();
    for (Entity comp : comps) {
      String id = (String) comp.getBody().get(ParameterConstants.STUDENT_SECTION_ASSOCIATION_ID);
      if (!secAssocIds.containsKey(id)) {
        secAssocIds.put(id, new HashSet<String>());
      }
      secAssocIds.get(id).add(comp.getEntityId());
    }

    Set<String> validSectionIds =
        sectionAssocValidator.validate(
            EntityNames.STUDENT_SECTION_ASSOCIATION, secAssocIds.keySet());

    return getValidIds(validSectionIds, secAssocIds);
  }
  /**
   * Generic function to list security events based on a NeutralQuery.
   *
   * <p>This is mostly the same as the corresponding method in BasicService, except that it does
   * security checking differently, based on the SecurityEventContextResolver.
   *
   * @param neutralQuery
   * @return
   */
  @Override
  public Iterable<EntityBody> list(NeutralQuery neutralQuery) {
    neutralQuery.setSortBy("timeStamp");
    neutralQuery.setSortOrder(NeutralQuery.SortOrder.descending);

    return super.list(neutralQuery);
  }
  @Test
  public void testUpdateRetry() {
    TenantContext.setTenantId("SLIUnitTest");
    repository.deleteAll("student", null);

    DBObject indexKeys = new BasicDBObject("body.cityOfBirth", 1);
    mongoTemplate.getCollection("student").ensureIndex(indexKeys);

    repository.create("student", buildTestStudentEntity());

    Entity entity = repository.findOne("student", new NeutralQuery());
    Map<String, Object> studentBody = entity.getBody();
    studentBody.put("cityOfBirth", "ABC");

    Entity studentEntity =
        new MongoEntity("student", entity.getEntityId(), studentBody, entity.getMetaData());
    repository.updateWithRetries("student", studentEntity, 5);

    NeutralQuery neutralQuery = new NeutralQuery();
    neutralQuery.addCriteria(new NeutralCriteria("cityOfBirth=ABC"));
    assertEquals(1, repository.count("student", neutralQuery));

    repository.deleteAll("student", null);
    mongoTemplate.getCollection("student").dropIndex(indexKeys);
  }
  @Test
  public void findOneMultipleMatches() {
    repository.deleteAll("student", null);
    DBObject indexKeys = new BasicDBObject("body.firstName", 1);
    mongoTemplate.getCollection("student").ensureIndex(indexKeys);

    Map<String, Object> student = buildTestStudentEntity();
    student.put("firstName", "Jadwiga");
    this.repository.create("student", student);

    student = buildTestStudentEntity();
    student.put("firstName", "Jadwiga");
    this.repository.create("student", student);

    student = buildTestStudentEntity();
    student.put("firstName", "Jadwiga");
    this.repository.create("student", student);

    NeutralQuery neutralQuery = new NeutralQuery();
    neutralQuery.addCriteria(new NeutralCriteria("firstName=Jadwiga"));
    assertNotNull(this.repository.findOne("student", neutralQuery));

    repository.deleteAll("student", null);
    mongoTemplate.getCollection("student").dropIndex(indexKeys);
  }
  /**
   * Get the list of authorized apps for the user based on the user's LEA.
   *
   * <p>No additional filtering is done on the results. E.g. if a user is a non-admin, the admin
   * apps will still show up in the list, or if an app is disabled it will still show up.
   *
   * @param principal
   * @return list of app IDs, or null if it couldn't be determined
   */
  @SuppressWarnings("unchecked")
  public boolean isAuthorizedForApp(Entity app, SLIPrincipal principal) {

    if (principal.isAdminRealmAuthenticated()) {
      return isAdminVisible(app);
    } else {
      if (isAutoAuthorized(app)) {
        return true;
      } else if (!isOperatorApproved(app)) {
        return false;
      } else {
        Set<String> edOrgs = helper.locateDirectEdorgs(principal.getEntity());
        NeutralQuery appAuthCollQuery = new NeutralQuery();
        appAuthCollQuery.addCriteria(new NeutralCriteria("applicationId", "=", app.getEntityId()));
        appAuthCollQuery.addCriteria(
            new NeutralCriteria("edorgs.authorizedEdorg", NeutralCriteria.CRITERIA_IN, edOrgs));
        Entity authorizedApps = repo.findOne("applicationAuthorization", appAuthCollQuery);
        if (authorizedApps != null) {
          if (isAutoApproved(app)) {
            return true;
          } else {
            // query approved edorgs
            List<String> approvedDistricts =
                new ArrayList<String>((List<String>) app.getBody().get("authorized_ed_orgs"));
            List<String> myDistricts = helper.getDistricts(edOrgs);
            approvedDistricts.retainAll(myDistricts);
            return !approvedDistricts.isEmpty();
          }
        }
      }
    }
    return false;
  }
  @Override
  public boolean validate(String entityType, Set<String> ids) throws IllegalStateException {
    if (!areParametersValid(EntityNames.PROGRAM, entityType, ids)) {
      return false;
    }

    NeutralQuery nq =
        new NeutralQuery(
            new NeutralCriteria(
                "body.staffId",
                "=",
                SecurityUtil.getSLIPrincipal().getEntity().getEntityId(),
                false));

    if (SecurityUtil.getSLIPrincipal().isStudentAccessFlag()) {
      nq.addCriteria(
          new NeutralCriteria(
              ParameterConstants.STUDENT_RECORD_ACCESS, NeutralCriteria.OPERATOR_EQUAL, true));
    }

    // Fetch associations
    addEndDateToQuery(nq, false);

    Set<String> programsToValidate = new HashSet<String>(ids);
    Iterable<Entity> assocs = getRepo().findAll(EntityNames.STAFF_PROGRAM_ASSOCIATION, nq);
    for (Entity assoc : assocs) {
      programsToValidate.remove((String) assoc.getBody().get("programId"));
    }

    return programsToValidate.isEmpty();
  }
  @Test
  public void testCRUDEntityRepository() {

    // clean up the existing student data
    repository.deleteAll("student", null);

    // create new student entity
    Map<String, Object> student = buildTestStudentEntity();

    // test save
    Entity saved = repository.create("student", student);
    String id = saved.getEntityId();
    assertTrue(!id.equals(""));

    // test findAll
    NeutralQuery neutralQuery = new NeutralQuery();
    neutralQuery.setOffset(0);
    neutralQuery.setLimit(20);
    Iterable<Entity> entities = repository.findAll("student", neutralQuery);
    assertNotNull(entities);
    Entity found = entities.iterator().next();
    assertEquals(found.getBody().get("birthDate"), student.get("birthDate"));
    assertEquals((found.getBody()).get("firstName"), "Jane");
    assertEquals((found.getBody()).get("lastName"), "Doe");

    // test find by id
    Entity foundOne = repository.findById("student", saved.getEntityId());
    assertNotNull(foundOne);
    assertEquals(foundOne.getBody().get("birthDate"), student.get("birthDate"));
    assertEquals((found.getBody()).get("firstName"), "Jane");

    // test update
    found.getBody().put("firstName", "Mandy");
    assertTrue(repository.update("student", found, false));
    entities = repository.findAll("student", neutralQuery);
    assertNotNull(entities);
    Entity updated = entities.iterator().next();
    assertEquals(updated.getBody().get("firstName"), "Mandy");

    // test delete by id
    Map<String, Object> student2Body = buildTestStudentEntity();
    Entity student2 = repository.create("student", student2Body);
    entities = repository.findAll("student", neutralQuery);
    assertNotNull(entities.iterator().next());
    repository.delete("student", student2.getEntityId());
    Entity zombieStudent = repository.findById("student", student2.getEntityId());
    assertNull(zombieStudent);

    assertFalse(repository.delete("student", student2.getEntityId()));

    // test deleteAll by entity type
    repository.deleteAll("student", null);
    entities = repository.findAll("student", neutralQuery);
    assertFalse(entities.iterator().hasNext());
  }
  private Set<String> getDirectChildLEAsOfEdOrg(String edOrgId) {
    Set<String> toReturn = new HashSet<String>();
    NeutralQuery query = new NeutralQuery(0);
    query.addCriteria(new NeutralCriteria("parentEducationAgencyReference", "=", edOrgId));

    for (Entity entity : repo.findAll(EntityNames.EDUCATION_ORGANIZATION, query)) {
      if (helper.isLEA(entity)) {
        toReturn.add(entity.getEntityId());
      }
    }
    return toReturn;
  }
 boolean schoolLineageIs(String schoolId, Set<String> expectedEdOrgs) {
   NeutralQuery neutralQuery = new NeutralQuery();
   neutralQuery.addCriteria(new NeutralCriteria("_id", NeutralCriteria.OPERATOR_EQUAL, schoolId));
   Entity school = repository.findOne(EntityNames.EDUCATION_ORGANIZATION, neutralQuery);
   ArrayList<String> edOrgs = (ArrayList<String>) school.getMetaData().get("edOrgs");
   if (edOrgs == null) {
     return expectedEdOrgs.isEmpty();
   } else if (!expectedEdOrgs.equals(new HashSet<String>(edOrgs))) {
     System.out.println(
         "School edOrg lineage incorrect. Expected " + expectedEdOrgs + ", got " + edOrgs);
     return false;
   }
   return true;
 }
  @Test
  public void findOneTestNegative() {
    repository.deleteAll("student", null);
    DBObject indexKeys = new BasicDBObject("body.firstName", 1);
    mongoTemplate.getCollection("student").ensureIndex(indexKeys);

    NeutralQuery neutralQuery = new NeutralQuery();
    neutralQuery.addCriteria(new NeutralCriteria("firstName=Jadwiga"));

    assertNull(this.repository.findOne("student", neutralQuery));

    repository.deleteAll("student", null);
    mongoTemplate.getCollection("student").dropIndex(indexKeys);
  }
  @Test
  public void testFindIdsByQuery() {
    repository.deleteAll("student", null);
    repository.create("student", buildTestStudentEntity());
    repository.create("student", buildTestStudentEntity());
    repository.create("student", buildTestStudentEntity());
    repository.create("student", buildTestStudentEntity());
    repository.create("student", buildTestStudentEntity());
    NeutralQuery neutralQuery = new NeutralQuery();
    neutralQuery.setOffset(0);
    neutralQuery.setLimit(100);

    Iterable<String> ids = repository.findAllIds("student", neutralQuery);
    List<String> idList = new ArrayList<String>();
    for (String id : ids) {
      idList.add(id);
    }

    assertEquals(5, idList.size());
  }
  /**
   * Uses the {@link SecurityEventContextResolver} to add conditions to neutralQuery to limit the
   * security events which the current user can view.
   *
   * @param neutralQuery
   */
  @Override
  protected void listSecurityCheck(NeutralQuery neutralQuery) {
    Set<String> idsToFilter = new HashSet<String>();
    idsToFilter.addAll(securityEventContextResolver.findAccessible(null));

    // At present, multiple "in" queries do not get joined together properly by the Mongo Query
    // converter.
    // Therefore, explicitly check for pre-existing "in" queries or "=" queries and combine them
    // here manually.

    boolean foundIdInQuery = false;
    for (NeutralCriteria criterion : neutralQuery.getCriteria()) {
      if ("_id".equals(criterion.getKey())) {
        foundIdInQuery = true;
        if (NeutralCriteria.CRITERIA_IN.equals(criterion.getOperator())) {
          Set<String> values = new HashSet<String>();
          for (String idValue : (Iterable<String>) criterion.getValue()) {
            values.add(idValue);
          }
          values.retainAll(idsToFilter);
          criterion.setValue(values);
        } else if (NeutralCriteria.OPERATOR_EQUAL.equals(criterion.getOperator())) {
          Set<String> values = new HashSet<String>();
          values.add((String) criterion.getValue());
          values.retainAll(idsToFilter);
          criterion.setOperator(NeutralCriteria.CRITERIA_IN);
          criterion.setValue(values);
        } else {
          throw new UnsupportedOperationException(
              "do not know how to handle additional _id criterion with operator "
                  + criterion.getOperator());
        }
      }
    }

    if (!foundIdInQuery) {
      neutralQuery.addCriteria(
          new NeutralCriteria("_id", NeutralCriteria.CRITERIA_IN, idsToFilter));
    }
  }
  @Test
  public void testCount() {
    repository.deleteAll("student", null);

    DBObject indexKeys = new BasicDBObject("body.cityOfBirth", 1);
    mongoTemplate.getCollection("student").ensureIndex(indexKeys);

    repository.create("student", buildTestStudentEntity());
    repository.create("student", buildTestStudentEntity());
    repository.create("student", buildTestStudentEntity());
    repository.create("student", buildTestStudentEntity());
    Map<String, Object> oddStudent = buildTestStudentEntity();
    oddStudent.put("cityOfBirth", "Nantucket");
    repository.create("student", oddStudent);
    assertEquals(5, repository.count("student", new NeutralQuery()));
    NeutralQuery neutralQuery = new NeutralQuery();
    neutralQuery.addCriteria(new NeutralCriteria("cityOfBirth=Nantucket"));
    assertEquals(1, repository.count("student", neutralQuery));

    repository.deleteAll("student", null);
    mongoTemplate.getCollection("student").dropIndex(indexKeys);
  }
  @Test
  public void testDeleteAll() {
    repository.deleteAll("student", null);

    DBObject indexKeys = new BasicDBObject("body.firstName", 1);
    mongoTemplate.getCollection("student").ensureIndex(indexKeys);

    Map<String, Object> studentMap = buildTestStudentEntity();
    studentMap.put("firstName", "John");
    repository.create("student", buildTestStudentEntity());
    repository.create("student", buildTestStudentEntity());
    repository.create("student", buildTestStudentEntity());
    repository.create("student", buildTestStudentEntity());
    repository.create("student", studentMap);
    assertEquals(5, repository.count("student", new NeutralQuery()));
    NeutralQuery neutralQuery = new NeutralQuery();
    neutralQuery.addCriteria(new NeutralCriteria("firstName=John"));
    repository.deleteAll("student", neutralQuery);
    assertEquals(4, repository.count("student", new NeutralQuery()));

    repository.deleteAll("student", null);
    mongoTemplate.getCollection("student").dropIndex(indexKeys);
  }
  @SuppressWarnings("unchecked")
  @Test
  public void testSort() {

    // clean up the existing student data
    repository.deleteAll("student", null);

    // create new student entity
    Map<String, Object> body1 = buildTestStudentEntity();
    Map<String, Object> body2 = buildTestStudentEntity();
    Map<String, Object> body3 = buildTestStudentEntity();
    Map<String, Object> body4 = buildTestStudentEntity();

    body1.put("firstName", "Austin");
    body2.put("firstName", "Jane");
    body3.put("firstName", "Mary");
    body4.put("firstName", "Suzy");

    body1.put("performanceLevels", new String[] {"1"});
    body2.put("performanceLevels", new String[] {"2"});
    body3.put("performanceLevels", new String[] {"3"});
    body4.put("performanceLevels", new String[] {"4"});

    // save entities
    repository.create("student", body1);
    repository.create("student", body2);
    repository.create("student", body3);
    repository.create("student", body4);

    // sort entities by firstName with ascending order
    NeutralQuery sortQuery1 = new NeutralQuery();
    sortQuery1.setSortBy("firstName");
    sortQuery1.setSortOrder(NeutralQuery.SortOrder.ascending);
    sortQuery1.setOffset(0);
    sortQuery1.setLimit(100);

    Iterable<Entity> entities = repository.findAll("student", sortQuery1);
    assertNotNull(entities);
    Iterator<Entity> it = entities.iterator();
    assertEquals("Austin", it.next().getBody().get("firstName"));
    assertEquals("Jane", it.next().getBody().get("firstName"));
    assertEquals("Mary", it.next().getBody().get("firstName"));
    assertEquals("Suzy", it.next().getBody().get("firstName"));

    // sort entities by firstName with descending order
    NeutralQuery sortQuery2 = new NeutralQuery();
    sortQuery2.setSortBy("firstName");
    sortQuery2.setSortOrder(NeutralQuery.SortOrder.descending);
    sortQuery2.setOffset(0);
    sortQuery2.setLimit(100);
    entities = repository.findAll("student", sortQuery2);
    assertNotNull(entities);
    it = entities.iterator();
    assertEquals("Suzy", it.next().getBody().get("firstName"));
    assertEquals("Mary", it.next().getBody().get("firstName"));
    assertEquals("Jane", it.next().getBody().get("firstName"));
    assertEquals("Austin", it.next().getBody().get("firstName"));

    // sort entities by performanceLevels which is an array with ascending
    // order
    NeutralQuery sortQuery3 = new NeutralQuery();
    sortQuery3.setSortBy("performanceLevels");
    sortQuery3.setSortOrder(NeutralQuery.SortOrder.ascending);
    sortQuery3.setOffset(0);
    sortQuery3.setLimit(100);
    entities = repository.findAll("student", sortQuery3);
    assertNotNull(entities);
    it = entities.iterator();
    assertEquals("1", ((List<String>) (it.next().getBody().get("performanceLevels"))).get(0));
    assertEquals("2", ((List<String>) (it.next().getBody().get("performanceLevels"))).get(0));
    assertEquals("3", ((List<String>) (it.next().getBody().get("performanceLevels"))).get(0));
    assertEquals("4", ((List<String>) (it.next().getBody().get("performanceLevels"))).get(0));

    // sort entities by performanceLevels which is an array with descending
    // order
    NeutralQuery sortQuery4 = new NeutralQuery();
    sortQuery4.setSortBy("performanceLevels");
    sortQuery4.setSortOrder(NeutralQuery.SortOrder.descending);
    sortQuery4.setOffset(0);
    sortQuery4.setLimit(100);
    entities = repository.findAll("student", sortQuery4);
    assertNotNull(entities);
    it = entities.iterator();
    assertEquals("4", ((List<String>) (it.next().getBody().get("performanceLevels"))).get(0));
    assertEquals("3", ((List<String>) (it.next().getBody().get("performanceLevels"))).get(0));
    assertEquals("2", ((List<String>) (it.next().getBody().get("performanceLevels"))).get(0));
    assertEquals("1", ((List<String>) (it.next().getBody().get("performanceLevels"))).get(0));
  }
  @Override
  public SLIPrincipal locate(
      String tenantId, String externalUserId, String userType, String clientId) {
    LOG.info("Locating user {}@{} of type: {}", new Object[] {externalUserId, tenantId, userType});
    SLIPrincipal user = new SLIPrincipal(externalUserId + "@" + tenantId);
    user.setExternalId(externalUserId);
    user.setTenantId(tenantId);
    user.setUserType(userType);

    TenantContext.setTenantId(tenantId);

    if (EntityNames.STUDENT.equals(userType)) {
      NeutralQuery neutralQuery =
          new NeutralQuery(
              new NeutralCriteria(
                  ParameterConstants.STUDENT_UNIQUE_STATE_ID,
                  NeutralCriteria.OPERATOR_EQUAL,
                  externalUserId));
      neutralQuery.setOffset(0);
      neutralQuery.setLimit(1);
      user.setEntity(repo.findOne(EntityNames.STUDENT, neutralQuery, true));
    } else if (EntityNames.PARENT.equals(userType)) {
      NeutralQuery neutralQuery =
          new NeutralQuery(
              new NeutralCriteria(
                  ParameterConstants.PARENT_UNIQUE_STATE_ID,
                  NeutralCriteria.OPERATOR_EQUAL,
                  externalUserId));
      neutralQuery.setOffset(0);
      neutralQuery.setLimit(1);
      user.setEntity(repo.findOne(EntityNames.PARENT, neutralQuery, true));
    } else if (isStaff(userType)) {

      NeutralQuery neutralQuery = new NeutralQuery();
      neutralQuery.setOffset(0);
      neutralQuery.setLimit(1);
      neutralQuery.addCriteria(
          new NeutralCriteria(
              ParameterConstants.STAFF_UNIQUE_STATE_ID,
              NeutralCriteria.OPERATOR_EQUAL,
              externalUserId));

      Iterable<Entity> staff = repo.findAll(EntityNames.STAFF, neutralQuery);

      if (staff != null && staff.iterator().hasNext()) {
        Entity entity = staff.iterator().next();
        Set<String> edorgs = edorgHelper.locateDirectEdorgs(entity);
        if (edorgs.size() == 0) {
          LOG.warn("User {} is not currently associated to a school/edorg", user.getId());
          throw new APIAccessDeniedException(
              "User is not currently associated to a school/edorg", user, clientId);
        }
        user.setEntity(entity);
      }
    }

    if (user.getEntity() == null) {
      LOG.warn("Failed to locate user {} in the datastore", user.getId());
      Entity entity =
          new MongoEntity(
              "user",
              SLIPrincipal.NULL_ENTITY_ID,
              new HashMap<String, Object>(),
              new HashMap<String, Object>());
      user.setEntity(entity);
    } else {
      LOG.info(
          "Matched user: {}@{} -> {}",
          new Object[] {externalUserId, tenantId, user.getEntity().getEntityId()});
    }

    return user;
  }