/* * Copyright 2012 Shared Learning Collaborative, LLC * * Licensed 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.slc.sli.dashboard.client; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.apache.commons.lang.StringUtils; import org.scribe.exceptions.OAuthException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slc.sli.api.client.Entity; import org.slc.sli.api.client.Link; import org.slc.sli.api.client.SLIClient; import org.slc.sli.api.client.SLIClientException; import org.slc.sli.api.client.SLIClientFactory; import org.slc.sli.dashboard.entity.ConfigMap; import org.slc.sli.dashboard.entity.GenericEntity; import org.slc.sli.dashboard.entity.util.GenericEntityComparator; import org.slc.sli.dashboard.entity.util.GenericEntityEnhancer; import org.slc.sli.dashboard.util.Constants; import org.slc.sli.dashboard.util.ExecutionTimeLogger; import org.slc.sli.dashboard.util.JsonConverter; import org.slc.sli.dashboard.util.SecurityUtil; /** * This client will use the SDK client to communicate with the SLI API. * * @author dwalker * @author rbloh * @author iivanisevic * */ public class SDKAPIClient implements APIClient { private static final Logger LOGGER = LoggerFactory.getLogger(SDKAPIClient.class); private SLIClientFactory clientFactory; private String gracePeriod; /** * Wrapper for value for the custom store - value is expected json object vs primitive * */ public static class CustomEntityWrapper { String value; public CustomEntityWrapper(String value) { this.value = value; } } /* * ***************************************************** * API Client Interface Methods * ***************************************************** */ public SLIClientFactory getClientFactory() { return clientFactory; } public void setClientFactory(SLIClientFactory clientFactory) { this.clientFactory = clientFactory; } /** * Set the SLI configured grace period for historical access * * @param gracePeriod */ public void setGracePeriod(String gracePeriod) { this.gracePeriod = gracePeriod; } /** * Get the SLI configured grace period for historical access * * @return */ // @Override public String getGracePeriod() { return this.gracePeriod; } /** * Get a resource entity of a specified type which is identified by id and enriched using * optional parameters * * @param token * @param type * @param id * @param params * @return */ @Override public GenericEntity getEntity(String token, String type, String id, Map<String, String> params) { return this.readEntity(token, "/" + type + "/" + id + "?" + this.buildQueryString(params), id); } /** * Get a list of resource entities of a specified type which are identified by a list of ids and * enriched using optional parameters * * @param token * @param type * @param ids * @param params * @return */ @Override public List<GenericEntity> getEntities(String token, String type, String ids, Map<String, String> params) { return this.readEntityList(token, "/" + type + "/" + ids + "?" + this.buildQueryString(params), ids); } /** * Get user's home entity * * @param token * @return */ @Override public GenericEntity getHome(String token) { return this.readEntity(token, SDKConstants.HOME_ENTITY); } /** * Get the user's unique identifier * * @param token * @return */ @Override public String getId(String token) { GenericEntity homeEntity = this.getHome(token); if (homeEntity != null) { for (Link linkMap : homeEntity.getLinks()) { if (linkMap.getLinkName().equals(Constants.ATTR_SELF)) { return parseId(linkMap.getResourceURL().getPath()); } } } return null; } /** * Get EdOrg custom data * * @param token * @param id * @return */ @Override public ConfigMap getEdOrgCustomData(String token, String id) { GenericEntity ge = readCustomEntity(token, SDKConstants.EDORGS_ENTITY + id + SDKConstants.CUSTOM_DATA); return JsonConverter.fromJson((String) ge.get("config"), ConfigMap.class); } /** * Store EdOrg custom data * * @param token * @param id * @param configMap */ @Override public void putEdOrgCustomData(String token, String id, ConfigMap configMap) { GenericEntity configMapEntity = new GenericEntity(); configMapEntity.put("config", JsonConverter.toJson(configMap)); this.createEntity(token, SDKConstants.EDORGS_ENTITY + id + SDKConstants.CUSTOM_DATA, configMapEntity); } /** * Get a list of educational organizations using a list of ids * * @param token * @param ids * @param params * @return */ @Override public List<GenericEntity> getEducationalOrganizations(String token, List<String> ids, Map<String, String> params) { return this.readEntityList(token, SDKConstants.EDORGS_ENTITY + buildListString(ids) + "?" + this.buildQueryString(params), ids); } /** * Get education organizations for staff member identified by id * * @param token * @param staffId * @return */ @Override public List<GenericEntity> getEducationOrganizationsForStaff(String token, String staffId) { return this.readEntityList(token, SDKConstants.STAFF_ENTITY + staffId + SDKConstants.STAFF_EDORG_ASSIGNMENT_ASSOC + SDKConstants.EDORGS, staffId); } /** * Get an educational organization identified by id * * @param token * @param id * @return */ @Override public GenericEntity getEducationalOrganization(String token, String id) { return this.readEntity(token, SDKConstants.EDORGS_ENTITY + id, id); } /** * Get education organizations for staff member identified by id and matching organization * category or first if not specified * * @param token * @param staffId * @param organizationCategory * @return */ @Override public GenericEntity getEducationOrganizationForStaff(String token, String staffId, String organizationCategory) { GenericEntity staffEdOrg = null; List<GenericEntity> edOrgs = this.readEntityList(token, SDKConstants.STAFF_ENTITY + staffId + SDKConstants.STAFF_EDORG_ASSIGNMENT_ASSOC + SDKConstants.EDORGS, staffId); if ((organizationCategory != null) && (organizationCategory.length() > 0)) { for (GenericEntity edOrg : edOrgs) { List<String> edOrgCategories = (List<String>) edOrg.get(Constants.ATTR_ORG_CATEGORIES); if (edOrgCategories != null && edOrgCategories.size() > 0) { for (String edOrgCategory : edOrgCategories) { if (edOrgCategory.equals(organizationCategory)) { staffEdOrg = edOrg; break; } } } } } else if (edOrgs.size() > 0) { staffEdOrg = edOrgs.get(0); } return staffEdOrg; } /** * Get parent educational organizations for the supplied edOrgs * * @param token * @param educationalOrganizations * @return */ @Override public List<GenericEntity> getParentEducationalOrganizations(String token, List<GenericEntity> educationalOrganizations) { List<String> ids = this.extractAttributesFromEntities(educationalOrganizations, Constants.ATTR_PARENT_EDORG); return this.getEducationalOrganizations(token, ids, null); } /** * Get parent educational organization for the supplied edOrg * * @param token * @param educationalOrganization * @return */ @Override public GenericEntity getParentEducationalOrganization(String token, GenericEntity educationalOrganization) { GenericEntity parentEducationOrganization = null; List<GenericEntity> educationalOrganizations = new ArrayList<GenericEntity>(); educationalOrganizations.add(educationalOrganization); List<String> ids = this.extractAttributesFromEntities(educationalOrganizations, Constants.ATTR_PARENT_EDORG); if (ids.size() > 0) { String parentId = ids.get(0); parentEducationOrganization = this.getEducationalOrganization(token, parentId); } return parentEducationOrganization; } /** * Get a list of all schools depending upon user role * * @param token * @param ids * @return */ @Override public List<GenericEntity> getSchools(String token, List<String> ids) { // get schools List<GenericEntity> schools = this.readEntityList(token, SDKConstants.SCHOOLS_ENTITY + "?" + this.buildQueryString(null)); return schools; } /** * Attempt to get a list of the users's schools * * Note: This is a naieve method, it assumes you're a teacher, and if that fails * it takes the route of going through pretending you're generic staff. * * This adds 1 extra API call. * * @param token * @param ids * @return a list of school entities that the user is associated with */ @Override public List<GenericEntity> getMySchools(String token) { List<GenericEntity> schools = new ArrayList<GenericEntity>(); // get schools schools = this.readEntityList(token, SDKConstants.TEACHERS_ENTITY + getId(token) + SDKConstants.TEACHER_SCHOOL_ASSOC + SDKConstants.SCHOOLS_ENTITY + "?" + this.buildQueryString(null)); if (schools == null || schools.size() == 0) { // Ok there are 5 potential edOrg levels so we need to get the edOrg for this staff then // dig down to the individual schools. Set<GenericEntity> schoolSet = new HashSet<GenericEntity>(); List<GenericEntity> edOrgs = new ArrayList<GenericEntity>(); schools = new ArrayList<GenericEntity>(); edOrgs.addAll(this.readEntityList(token, SDKConstants.STAFF_ENTITY + getId(token) + SDKConstants.STAFF_EDORG_ASSIGNMENT_ASSOC + SDKConstants.EDORGS_ENTITY + "?" + this.buildQueryString(null))); for (int i = 0; i < edOrgs.size(); ++i) { GenericEntity edOrg = edOrgs.get(i); Map<String, String> query = new HashMap<String, String>(); query.put("parentEducationAgencyReference", (String) edOrg.get("id")); List<String> categories = edOrg.getList("organizationCategories"); if (categories.contains("School")) { schoolSet.add(edOrg); } else { List<GenericEntity> newEdorgs = this.readEntityList(token, SDKConstants.EDORGS_ENTITY + "?" + this.buildQueryString(query)); if (newEdorgs != null) { edOrgs.addAll(newEdorgs); } } } schools.addAll(schoolSet); } return schools; } /** * Get a list of schools using a list of ids * * @param token * @param ids * @param params * @return */ @Override public List<GenericEntity> getSchools(String token, List<String> ids, Map<String, String> params) { return this.readEntityList(token, SDKConstants.SCHOOLS_ENTITY + buildListString(ids) + "?" + this.buildQueryString(params), ids); } /** * Get a school identified by id * * @param token * @param id * @return */ @Override public GenericEntity getSchool(String token, String id) { return this.readEntity(token, SDKConstants.SCHOOLS_ENTITY + id, id); } /** * Get a list of all sessions * * @param token * @param params * @return */ @Override public List<GenericEntity> getSessions(String token, String schoolId, Map<String, String> params) { String url = ""; if (schoolId != null) { url = SDKConstants.SCHOOLS_ENTITY + schoolId; } if (params != null && !params.isEmpty()) { url += SDKConstants.SESSIONS_ENTITY + "?" + this.buildQueryString(params); } else { url += SDKConstants.SESSIONS_ENTITY; } return this.readEntityList(token, url); } /** * Get a list of sessions using a list of ids * * @param token * @param ids * @param params * @return */ @Override public List<GenericEntity> getSessions(String token, List<String> ids, Map<String, String> params) { return this.readEntityList(token, SDKConstants.SESSIONS_ENTITY + buildListString(ids) + "?" + this.buildQueryString(params), ids); } /** * Get a list of sessions for the specified school year * * @param token * @param schoolYear * @return */ @Override public List<GenericEntity> getSessionsForYear(String token, String schoolYear) { Map<String, String> params = new HashMap<String, String>(); params.put("schoolYear", schoolYear); return this.readEntityList(token, SDKConstants.SESSIONS_ENTITY + "?" + this.buildQueryString(params)); } /** * Get a session identified by id * * @param token * @param id * @return */ @Override public GenericEntity getSession(String token, String id) { return this.readEntity(token, SDKConstants.SESSIONS_ENTITY + id, id); } /** * Get a list of all sections * * @param token * @param params * @return */ @Override public List<GenericEntity> getSections(String token, Map<String, String> params) { return this.readEntityList(token, SDKConstants.SECTIONS_ENTITY + "?" + this.buildQueryString(params)); } /** * Get a list of sections using a list of ids * * @param token * @param ids * @param params * @return */ @Override public List<GenericEntity> getSections(String token, List<String> ids, Map<String, String> params) { return this.readEntityList(token, SDKConstants.SECTIONS_ENTITY + buildListString(ids) + "?" + this.buildQueryString(params), ids); } /** * Get all sections for a non-Educator * * @param token * @param params * @return */ @Override public List<GenericEntity> getSectionsForNonEducator(String token, Map<String, String> params) { List<GenericEntity> sections = this.getSections(token, params); // Enrich sections with session details enrichSectionsWithSessionDetails(token, null, sections); // Enable filtering sections = filterCurrentSections(sections, true); return sections; } /** * Get all sections for a Teacher * * @param token * @param teacherId * @param params * @return */ @Override public List<GenericEntity> getSectionsForTeacher(String teacherId, String token, Map<String, String> params) { List<GenericEntity> sections = this.readEntityList(token, SDKConstants.TEACHERS_ENTITY + teacherId + SDKConstants.TEACHER_SECTION_ASSOC + SDKConstants.SECTIONS_ENTITY + "?" + this.buildQueryString(params), teacherId); // Disable filtering, so just adding section codes to sections with no name sections = filterCurrentSections(sections, false); return sections; } /** * Get a list of sections for the given student id * * @param token * @param studentId * @param params * @return */ @Override public List<GenericEntity> getSectionsForStudent(final String token, final String studentId, Map<String, String> params) { //params.put(Constants.ATTR_SELECTOR_FIELD, ":(.,studentSectionAssociations)"); List<GenericEntity> studentSectionAssociations = getStudentSectionAssociations(token, studentId, params); List<GenericEntity> sections = this.readEntityList(token, SDKConstants.STUDENTS_ENTITY + studentId + SDKConstants.STUDENT_SECTION_ASSOC + SDKConstants.SECTIONS_ENTITY + "?" + this.buildQueryString(params), studentId); sections = mergeLists(sections, studentSectionAssociations, "id", "sectionId", "studentSectionAssociations"); // Disable filtering, so just adding section codes to sections with no name sections = filterCurrentSections(sections, false); return sections; } private List<GenericEntity> getStudentSectionAssociations(final String token, final String studentId, Map<String, String> params) { List<GenericEntity> studentSectionAssociations = this.readEntityList(token, SDKConstants.STUDENTS_ENTITY + studentId + SDKConstants.STUDENT_SECTION_ASSOC, studentId); return studentSectionAssociations; } private List<GenericEntity> mergeLists(List<GenericEntity> entities, List<GenericEntity> associations, String key, String associationKey, String attributeName) { List<GenericEntity> results = new ArrayList<GenericEntity>(); for (GenericEntity entity : entities) { String id = (String) entity.get(key); List<GenericEntity> subList = getSubList(associations, associationKey, id); entity.put(attributeName, subList); results.add(entity); } return results; } private List<GenericEntity> getSubList(List<GenericEntity> entities, String key, String value) { List<GenericEntity> results = new ArrayList<GenericEntity>(); for (GenericEntity entity : entities) { if (entity.containsKey(key)) { if (entity.get(key).equals(value)) { results.add(entity); } } } return results; } /** * Get a section identified by id * * @param token * @param id * @return */ @Override public GenericEntity getSection(String token, String id) { GenericEntity section = this.readEntity(token, SDKConstants.SECTIONS_ENTITY + id); ensureSectionName(section); return section; } /** * Get student home room information * * @param token * @param studentId * @return */ @Override public GenericEntity getSectionHomeForStudent(String token, String studentId) { GenericEntity homeRoomEntity = null; List<GenericEntity> studentSections = this.getSectionsForStudent(token, studentId, new HashMap<String, String>()); // If only one section association exists for the student, return the // section as home room if (studentSections.size() == 1) { homeRoomEntity = studentSections.get(0); return homeRoomEntity; } // If multiple section associations exist for the student, return the // section with homeroomIndicator set to true for (GenericEntity studentSection : studentSections) { List<Map<String, Object>> studentSectionAssocs = (List<Map<String, Object>>) studentSection .get("studentSectionAssociations"); if (studentSectionAssocs != null) { for (Map<String, Object> sectionAssoc : studentSectionAssocs) { if ((sectionAssoc.get(Constants.ATTR_HOMEROOM_INDICATOR) != null) && ((Boolean) sectionAssoc.get(Constants.ATTR_HOMEROOM_INDICATOR)) && sectionAssoc.get(Constants.ATTR_STUDENT_ID).equals(studentId)) { homeRoomEntity = studentSection; return homeRoomEntity; } } } } return homeRoomEntity; } /** * Get a list of courses using a list of ids * * @param token * @param ids * @param params * @return */ @Override public List<GenericEntity> getCourses(String token, List<String> ids, Map<String, String> params) { return this.readEntityList(token, SDKConstants.COURSES_ENTITY + buildListString(ids) + "?" + this.buildQueryString(params), ids); } /** * Get a list of courses for the given student id * * @param token * @param studentId * @param params * @return */ @Override public List<GenericEntity> getCoursesForStudent(String token, String studentId, Map<String, String> params) { params.put("optionalFields", "transcript"); addGradeLevelParam(params); return this.readEntityList(token, SDKConstants.SECTIONS_ENTITY + studentId + SDKConstants.STUDENT_SECTION_ASSOC + SDKConstants.STUDENTS + "?" + this.buildQueryString(params), studentId); } // @Override @Override public List<GenericEntity> getCoursesSectionsForSchool(String token, String schoolId) { // get sections List<GenericEntity> sections = null; if (SecurityUtil.isNotEducator()) { sections = this.readEntityList(token, SDKConstants.SCHOOLS_ENTITY + schoolId + SDKConstants.SECTIONS + "?" + Constants.LIMIT + "=" + Constants.MAX_RESULTS); enrichSectionsWithSessionDetails(token, schoolId, sections); sections = filterCurrentSections(sections, true); } else { String teacherId = getId(token); sections = getSectionsForTeacher(teacherId, token, null); // filter by school id if (schoolId != null) { List<GenericEntity> filteredSections = new ArrayList<GenericEntity>(); for (GenericEntity section : sections) { if (section.getString(Constants.ATTR_SCHOOL_ID) != null && section.getString(Constants.ATTR_SCHOOL_ID).equals(schoolId)) { filteredSections.add(section); } } sections = filteredSections; } } // get courses List<GenericEntity> courses = new ArrayList<GenericEntity>(); if (sections != null && !sections.isEmpty()) { courses = getCourseSectionMappings(sections, schoolId, token); } return courses; } /** * Get a list of transcripts for the given student id * * @param token * @param studentId * @param params * @return */ @Override public List<GenericEntity> getTranscriptsForStudent(String token, String studentId, Map<String, String> params) { return this.readEntityList(token, SDKConstants.STUDENTS_ENTITY + studentId + SDKConstants.COURSE_TRANSCRIPTS + "?" + this.buildQueryString(params), studentId); } /** * Get a course identified by id * * @param token * @param id * @return */ @Override public GenericEntity getCourse(String token, String id) { return this.readEntity(token, SDKConstants.COURSES_ENTITY + id, id); } /** * Get a list of staff members using a list of ids * * @param token * @param ids * @param params * @return */ @Override public List<GenericEntity> getStaff(String token, List<String> ids, Map<String, String> params) { return this.readEntityList(token, SDKConstants.STAFF_ENTITY + buildListString(ids) + "?" + this.buildQueryString(params), ids); } /** * Get staff member information identified by id * * @param token * @param id * @return */ @Override public GenericEntity getStaff(String token, String id) { return this.readEntity(token, SDKConstants.STAFF_ENTITY + id, id); } /** * Get staff member information identified by id along with specified education organization of * category * * @param token * @param id * @param organizationCategory * @return */ @Override public GenericEntity getStaffWithEducationOrganization(String token, String id, String organizationCategory) { GenericEntity staffEntity = this.getStaff(token, id); GenericEntity edOrgEntity = this.getEducationOrganizationForStaff(token, id, organizationCategory); if (edOrgEntity != null) { String edOrgSliId = edOrgEntity.getId(); staffEntity.put(SDKConstants.EDORG_SLI_ID_ATTRIBUTE, edOrgSliId); staffEntity.put(SDKConstants.EDORG_ATTRIBUTE, edOrgEntity); } return staffEntity; } /** * Get a list of teachers specified by a list of ids * * @param token * @param ids * @param params * @return */ @Override public List<GenericEntity> getTeachers(String token, List<String> ids, Map<String, String> params) { return this.readEntityList(token, SDKConstants.TEACHERS_ENTITY + buildListString(ids) + "?" + this.buildQueryString(params), ids); } /** * Get a teacher identified by id * * @param token * @param id * @return */ @Override public GenericEntity getTeacher(String token, String id) { // get Teacher information return this.readEntity(token, SDKConstants.TEACHERS_ENTITY + id, id); } @Override public GenericEntity getTeacherWithSections(String token, String id) { GenericEntity teacher = getTeacher(token, id); List<GenericEntity> sections = this.readEntityList(token, SDKConstants.TEACHERS_ENTITY + id + SDKConstants.TEACHER_SECTION_ASSOC + "/" + SDKConstants.SECTIONS.replaceAll("/", ""), id); if (sections != null && !sections.isEmpty()) { GenericEntityComparator sectionComparator = new GenericEntityComparator("uniqueSectionCode", String.class); Collections.sort(sections, sectionComparator); teacher.put("sections", sections); } return teacher; } /** * Get the teacher for a specified section * * @param token * @param sectionId * @return */ @Override public GenericEntity getTeacherForSection(String token, String sectionId) { GenericEntity teacher = null; List<GenericEntity> teacherSectionAssociations = this.readEntityList(token, SDKConstants.SECTIONS_ENTITY + sectionId + SDKConstants.TEACHER_SECTION_ASSOC + "?" + this.buildQueryString(null), sectionId); if (teacherSectionAssociations != null) { for (GenericEntity teacherSectionAssociation : teacherSectionAssociations) { if (teacherSectionAssociation.getString(Constants.ATTR_CLASSROOM_POSITION).equals( Constants.TEACHER_OF_RECORD)) { String teacherId = teacherSectionAssociation.getString(Constants.ATTR_TEACHER_ID); try { teacher = this.getTeacher(token, teacherId); } catch (Exception e) { LOGGER.error(e.getMessage()); } return teacher; } } } return teacher; } /** * Get a list of teachers for a specific school * * @param token * @param schoolId * @return */ @Override public List<GenericEntity> getTeachersForSchool(String token, String schoolId) { List<GenericEntity> teachers = this.readEntityList(token, SDKConstants.SCHOOLS_ENTITY.replaceAll("/", "") + "/" + schoolId + SDKConstants.TEACHER_SCHOOL_ASSOC + SDKConstants.TEACHERS_ENTITY.replaceAll("/", ""), schoolId); return teachers; } /** * Get a list of parents for the given student id * * @param token * @param studentId * @param params * @return */ @Override public List<GenericEntity> getParentsForStudent(String token, String studentId, Map<String, String> params) { //params.put(Constants.ATTR_SELECTOR_FIELD, ":(.,studentParentAssociations)"); List<GenericEntity> studentParentAssociations = getStudentParentAssociations(token, studentId, params); List<GenericEntity> parents = this.readEntityList(token, SDKConstants.STUDENTS_ENTITY + studentId + SDKConstants.STUDENT_PARENT_ASSOC + SDKConstants.PARENTS + "?" + this.buildQueryString(params), studentId); parents = mergeLists(parents, studentParentAssociations, "id", "parentId", "studentParentAssociations"); return parents; } private List<GenericEntity> getStudentParentAssociations(final String token, final String studentId, Map<String, String> params) { List<GenericEntity> studentParentAssociations = this.readEntityList(token, SDKConstants.STUDENTS_ENTITY + studentId + SDKConstants.STUDENT_PARENT_ASSOC, studentId); return studentParentAssociations; } /** * Get a list of students assigned to the specified school for a given params * * @param token * @param ids * @param params * @return */ @Override public List<GenericEntity> getStudentsForSchool(String token, String schoolId, Map<String, String> params) { addGradeLevelParam(params); return this.readEntityList(token, SDKConstants.SCHOOLS_ENTITY + schoolId + SDKConstants.STUDENT_SCHOOL_ASSOC + SDKConstants.STUDENTS.replaceAll("/", "") + "?" + this.buildQueryString(params)); } /** * Get a list of all students * * @param token * @param ids * @param params * @return */ @Override public List<GenericEntity> getStudents(String token, Map<String, String> params) { addGradeLevelParam(params); return this.readEntityList(token, SDKConstants.STUDENTS_ENTITY + "?" + this.buildQueryString(params)); } /** * Get a list of students specified by a list of ids * * @param token * @param ids * @param params * @return */ @Override public List<GenericEntity> getStudents(String token, List<String> ids, Map<String, String> params) { addGradeLevelParam(params); return this.readEntityList(token, SDKConstants.STUDENTS_ENTITY + buildListString(ids) + "?" + this.buildQueryString(params), ids); } private void addGradeLevelParam(Map<String, String> params) { String optionalParams; if (params.containsKey(SDKConstants.PARAM_OPTIONAL_FIELDS)) { optionalParams = params.get(SDKConstants.PARAM_OPTIONAL_FIELDS) + "," + Constants.ATTR_GRADE_LEVEL; } else { optionalParams = Constants.ATTR_GRADE_LEVEL; } params.put(SDKConstants.PARAM_OPTIONAL_FIELDS, optionalParams); } /** * Get a list of students assigned to the specified section * * @param token * @param sectionId * @return */ @Override public List<GenericEntity> getStudentsForSection(String token, String sectionId) { Map<String, String> params = new HashMap<String, String>(); String optionalParams = Constants.ATTR_ASSESSMENTS + "," + Constants.ATTR_STUDENT_ATTENDANCES_1 + "," + Constants.ATTR_TRANSCRIPT + "," + Constants.ATTR_GRADEBOOK + "," + Constants.ATTR_GRADE_LEVEL; params.put(SDKConstants.PARAM_OPTIONAL_FIELDS, optionalParams); return this.readEntityList(token, SDKConstants.SECTIONS_ENTITY + sectionId + SDKConstants.STUDENT_SECTION_ASSOC + SDKConstants.STUDENTS + "?" + this.buildQueryString(params), sectionId); } /** * Get a list of students using name search * * @param token * @param firstName * @param lastName * @return */ @Override public List<GenericEntity> getStudentsWithSearch(String token, String firstName, String lastName, String schoolId) { List<GenericEntity> students = null; Map<String, String> params = new HashMap<String, String>(); addGradeLevelParam(params); if ((firstName != null) && (firstName.length() > 0)) { params.put(SDKConstants.PARAM_FIRST_NAME, firstName); } if ((lastName != null) && (lastName.length() > 0)) { params.put(SDKConstants.PARAM_LAST_NAME, lastName); } if ((schoolId != null) && (schoolId.length() > 0)) { students = this.getStudentsForSchool(token, schoolId, params); } else { students = this.getStudents(token, params); } return students; } /** * Get a list of students in the specified section along with gradebook entries * * @param token * @param sectionId * @return */ @Override public List<GenericEntity> getStudentsForSectionWithGradebookEntries(String token, String sectionId) { Map<String, String> params = new HashMap<String, String>(); String optionalParams = Constants.ATTR_GRADEBOOK; params.put(SDKConstants.PARAM_OPTIONAL_FIELDS, optionalParams); addGradeLevelParam(params); return this.readEntityList(token, SDKConstants.SECTIONS_ENTITY + sectionId + SDKConstants.STUDENT_SECTION_ASSOC + SDKConstants.STUDENTS + "?" + this.buildQueryString(params), sectionId); } /** * Get a student identified by id * * @param token * @param id * @return */ @Override public GenericEntity getStudent(String token, String id) { Map<String, String> params = new HashMap<String, String>(); String optionalParams = Constants.ATTR_GRADE_LEVEL; params.put(SDKConstants.PARAM_OPTIONAL_FIELDS, optionalParams); return this.readEntity(token, SDKConstants.STUDENTS_ENTITY + id + "?" + this.buildQueryString(params), id); } /** * Get a student identified by id including specified optional information * * @param token * @param id * @param optionalFields * @return */ @Override public GenericEntity getStudentWithOptionalFields(String token, String id, List<String> optionalFields) { Map<String, String> params = new HashMap<String, String>(); String optionalParams = this.buildListString(optionalFields); params.put(SDKConstants.PARAM_OPTIONAL_FIELDS, optionalParams); addGradeLevelParam(params); return this.readEntity(token, SDKConstants.STUDENTS_ENTITY + id + "?" + this.buildQueryString(params), id); } /** * Get a list of school enrollments for the given student id * * @param token * @param student * @return */ @Override public List<GenericEntity> getEnrollmentForStudent(String token, String studentId) { Map<String, String> params = new HashMap<String, String>(); params.put(SDKConstants.PARAM_SORT_BY, SDKConstants.PARAM_ENTRY_DATE); params.put(SDKConstants.PARAM_SORT_ORDER, SDKConstants.PARAM_SORT_ORDER_DESCENDING); List<GenericEntity> studentSchoolAssociations = this.readEntityList(token, SDKConstants.STUDENTS_ENTITY + studentId + SDKConstants.STUDENT_SCHOOL_ASSOC + "?" + this.buildQueryString(params), studentId); for (GenericEntity studentSchoolAssociation : studentSchoolAssociations) { studentSchoolAssociation = GenericEntityEnhancer.enhanceStudentSchoolAssociation(studentSchoolAssociation); String schoolId = (String) studentSchoolAssociation.get(Constants.ATTR_SCHOOL_ID); // Retrieve the school for the corresponding student school association GenericEntity school = this.getSchool(token, schoolId); studentSchoolAssociation.put(Constants.ATTR_SCHOOL, school); } return studentSchoolAssociations; } /** * Get a list of attendances for the given student id * * @param token * @param studentId * @param params * @return */ @Override public List<GenericEntity> getAttendanceForStudent(String token, String studentId, Map<String, String> params) { return this.readEntityList(token, SDKConstants.STUDENTS_ENTITY + studentId + SDKConstants.ATTENDANCES_ENTITY + "?" + this.buildQueryString(params), studentId); } /** * Get a list of academic records for the given student id * * @param token * @param studentId * @param params * @return */ @Override public List<GenericEntity> getAcademicRecordsForStudent(String token, String studentId, Map<String, String> params) { if (params != null) { params.put(SDKConstants.PARAM_STUDENT_ID, studentId); } return this.readEntityList(token, SDKConstants.ACADEMIC_RECORDS_ENTITY + "?" + this.buildQueryString(params), studentId); } /** * Get a list of assessments using a list of ids * * @param token * @param ids * @param params * @return */ @Override public List<GenericEntity> getAssessments(String token, List<String> ids, Map<String, String> params) { return this.readEntityList(token, SDKConstants.ASSESSMENTS_ENTITY + buildListString(ids) + "?" + this.buildQueryString(params), ids); } /** * Get a list of assessments for the given student id * * @param token * @param studentId * @param params * @return */ @Override public List<GenericEntity> getAssessmentsForStudent(String token, String studentId) { return this.readEntityList(token, SDKConstants.STUDENTS_ENTITY + studentId + SDKConstants.STUDENT_ASSESSMENTS + "?" + this.buildQueryString(null), studentId); } /** * Get an assessment identified by id * * @param token * @param id * @return */ @Override public GenericEntity getAssessment(String token, String id) { return this.readEntity(token, SDKConstants.ASSESSMENTS_ENTITY + id, id); } /* * ***************************************************** * Core API SDK Methods * ***************************************************** */ /** * Read a custom entity using the SDK * * @param token * @param url * @param entityClass * @return */ @ExecutionTimeLogger.LogExecutionTime protected GenericEntity readCustomEntity(String token, String url) { GenericEntity entity = null; try { List<Entity> entityList = getClient(token).read(url); if (entityList.size() > 0) { entity = new GenericEntity(entityList.get(0)); } } catch (SLIClientException e) { return null; } catch (Exception e) { LOGGER.error("Exception occurred during API read", e); } return entity; } /** * Read a resource entity using the SDK * * @param token * @param url * @return */ @Override @ExecutionTimeLogger.LogExecutionTime public GenericEntity readEntity(String token, String url) { GenericEntity entity = null; try { List<Entity> entityList = getClient(token).read(url); if (entityList.size() > 0) { Entity theEntity = entityList.get(0); entity = new GenericEntity(theEntity); } } catch (Exception e) { LOGGER.error("Exception occurred during API read", e); } return entity; } /** * Read a resource entity using the SDK * * @param token * @param url * @return */ @ExecutionTimeLogger.LogExecutionTime protected GenericEntity readEntity(String token, String url, String id) { if ((id == null) || (id.length() <= 0)) { return null; } else { return readEntity(token, url); } } /** * Read a list of resource entities using the SDK * * @param token * @param url * @return */ @ExecutionTimeLogger.LogExecutionTime @Override public List<GenericEntity> readEntityList(String token, String url) { List<GenericEntity> genericEntities = new ArrayList<GenericEntity>(); try { List<Entity> entityList = getClient(token).read(url); for (Entity entity : entityList) { GenericEntity genericEntity = new GenericEntity(entity); genericEntity.put("links", mappifyLinks(entity.getLinks())); genericEntities.add(genericEntity); } } catch (SLIClientException e) { return Collections.EMPTY_LIST; } catch (Exception e) { LOGGER.error("Exception occurred during API read", e); } return genericEntities; } private List<Map<String, String>> mappifyLinks(List<Link> realLinks) { // somewhere some code is dying because the links in the SDK are being returned as Link // objects and not maps. I don't feel like digging through acres of code to find it, so I'm // putting in this hack List<Map<String, String>> mapLinks = new ArrayList<Map<String, String>>(); if (realLinks != null) { for (Link link : realLinks) { Map<String, String> mapLink = new HashMap<String, String>(); mapLink.put("rel", link.getLinkName()); mapLink.put("href", link.getResourceURL().toString()); mapLinks.add(mapLink); } } return mapLinks; } /** * Read a list of resource entities using the SDK. This method checks id for * null or size == 0 and returns Collections.emptyList iff true. * * @param token * @param url * @param id * @return */ @ExecutionTimeLogger.LogExecutionTime protected List<GenericEntity> readEntityList(String token, String url, List id) { if (id == null || id.size() == 0) { return Collections.emptyList(); } else { return readEntityList(token, url); } } /** * Read a list of resource entities using the SDK. This method checks id for * null or length == 0 and returns defaultList iff true. * * @param token * @param url * @param id * @return */ @ExecutionTimeLogger.LogExecutionTime protected List<GenericEntity> readEntityList(String token, String url, String id) { if (id == null || id.length() <= 0) { return Collections.emptyList(); } else { return readEntityList(token, url); } } /** * Create a resource entity using the SDK * * @param token * @param url * @param entity * @return */ @ExecutionTimeLogger.LogExecutionTime protected void createEntity(String token, String url, GenericEntity entity) { try { getClient(token).create(entity, url); } catch (Exception e) { LOGGER.error("Exception occurred during API create", e); } } /** * Update a resource entity using the SDK * * @param token * @param url * @param entity * @return */ @ExecutionTimeLogger.LogExecutionTime protected void updateEntity(String token, String url, GenericEntity entity) { try { getClient(token).update(entity); } catch (Exception e) { LOGGER.error("Exception occurred during API update", e); } } /** * Delete a resource entity using the SDK * * @param token * @param url * @param entity * @return */ @ExecutionTimeLogger.LogExecutionTime protected void deleteEntity(String token, Entity entity) { try { getClient(token).delete(entity); } catch (Exception e) { LOGGER.error("Exception occurred during API delete", e); } } /* * ***************************************************** * API Helper Methods * ***************************************************** */ /** * Given a link in the API response, extract the entity's unique id * * @param link * @return */ private String parseId(String path) { String id; int index = path.lastIndexOf("/"); id = path.substring(index + 1); return id; } /** * Extract the specified attribute's value from each entity in the given entity list * * @param entities * @param attributeName * @return */ private List<String> extractAttributesFromEntities(List<GenericEntity> entities, String attributeName) { List<String> attributeList = new ArrayList<String>(); if (entities != null) { for (GenericEntity entity : entities) { String attributeValue = (String) entity.get(attributeName); if ((attributeValue != null) && (attributeValue.length() > 0)) { attributeList.add(attributeValue); } } } return attributeList; } /** * Extract the link with the given relationship from an entity * * @param entity * @param rel * @return */ private List<String> extractLinksFromEntity(GenericEntity entity, String rel) { List<String> linkList = new ArrayList<String>(); if (entity != null && entity.containsKey(Constants.ATTR_LINKS)) { for (Map link : (List<Map>) (entity.get(Constants.ATTR_LINKS))) { if (link.get(Constants.ATTR_REL).toString().contains(rel)) { String href = (String) link.get(Constants.ATTR_HREF); linkList.add(href); } } } return linkList; } /** * Enrich section entities with session details to be leveraged during filtering * * @param token * @param sections */ private void enrichSectionsWithSessionDetails(String token, String schoolId, List<GenericEntity> sections) { List<GenericEntity> sessions = this.getSessions(token, schoolId, null); if ((sessions != null) && (sections != null)) { // Setup sessions lookup map Map<String, GenericEntity> sessionMap = new HashMap<String, GenericEntity>(); for (GenericEntity session : sessions) { sessionMap.put(session.getId(), session); } // Enrich each section with session entity for (GenericEntity section : sections) { String sessionIdAttribute = (String) section.get(Constants.ATTR_SESSION_ID); if (sessionIdAttribute != null) { GenericEntity session = sessionMap.get(sessionIdAttribute); section.put(Constants.ATTR_SESSION, session); } } } } /** * Process sections to ensure section name and filter historical data if specified * * @param sections * @param filterHistoricalData * @return */ private List<GenericEntity> filterCurrentSections(List<GenericEntity> sections, boolean filterHistoricalData) { List<GenericEntity> filteredSections = sections; if (filterHistoricalData) { filteredSections = new ArrayList<GenericEntity>(); } if (sections != null && sections.size() > 0) { // Setup grace period date Calendar gracePeriodCalendar = Calendar.getInstance(); gracePeriodCalendar.setTimeInMillis(System.currentTimeMillis()); try { if (gracePeriod != null && !gracePeriod.equals("")) { int daysToSubtract = Integer.parseInt(gracePeriod) * -1; gracePeriodCalendar.add(Calendar.DATE, daysToSubtract); } } catch (NumberFormatException exception) { LOGGER.warn("Invalid grace period: {}", exception.getMessage()); } for (GenericEntity section : sections) { // Ensure section name ensureSectionName(section); // Filter historical sections/sessions if necessary if (filterHistoricalData) { Map<String, Object> session = (Map<String, Object>) section.get(Constants.ATTR_SESSION); // Verify section has been enriched with session details if (session != null) { try { // Setup session end date String endDateAttribute = (String) session.get(Constants.ATTR_SESSION_END_DATE); DateFormat formatter = new SimpleDateFormat(Constants.ATTR_DATE_FORMAT); Date sessionEndDate = formatter.parse(endDateAttribute); Calendar sessionEndCalendar = Calendar.getInstance(); sessionEndCalendar.setTimeInMillis(sessionEndDate.getTime()); // Add filtered section if grace period adjusted date is before // or equal to session end date if (gracePeriodCalendar.compareTo(sessionEndCalendar) <= 0) { filteredSections.add(section); } } catch (IllegalArgumentException exception) { LOGGER.warn("Invalid session date formatter configuration: {}", exception.getMessage()); } catch (ParseException exception) { LOGGER.warn("Invalid session date format: {}", exception.getMessage()); } } } } } return filteredSections; } /** * Match schools and sections. Also retrieve course info. * * @param sections * @param token * @return */ private List<GenericEntity> matchSchoolsAndSections(List<GenericEntity> schools, List<GenericEntity> sections, String token) { // collect associated course first. HashMap<String, GenericEntity> courseMap = new HashMap<String, GenericEntity>(); HashMap<String, String> sectionIDToCourseIDMap = new HashMap<String, String>(); getCourseSectionsMappings(sections, token, courseMap, sectionIDToCourseIDMap); // now collect associated schools. HashMap<String, GenericEntity> schoolMap = new HashMap<String, GenericEntity>(); HashMap<String, String> sectionIDToSchoolIDMap = new HashMap<String, String>(); getSchoolSectionsMappings(sections, token, schools, schoolMap, sectionIDToSchoolIDMap); // Now associate course and school. // There is no direct course-school association in ed-fi. For any section associated to // a school, its course will also be associated. HashMap<String, HashSet<String>> schoolIDToCourseIDMap = new HashMap<String, HashSet<String>>(); if (sections != null) { for (int i = 0; i < sections.size(); i++) { GenericEntity section = sections.get(i); if (sectionIDToSchoolIDMap.containsKey(section.get(Constants.ATTR_ID)) && sectionIDToCourseIDMap.containsKey(section.get(Constants.ATTR_ID))) { String schoolId = sectionIDToSchoolIDMap.get(section.get(Constants.ATTR_ID)); String courseId = sectionIDToCourseIDMap.get(section.get(Constants.ATTR_ID)); if (!schoolIDToCourseIDMap.containsKey(schoolId)) { schoolIDToCourseIDMap.put(schoolId, new HashSet<String>()); } schoolIDToCourseIDMap.get(schoolId).add(courseId); } } } // now create the generic entity for (String schoolId : schoolIDToCourseIDMap.keySet()) { GenericEntity s = schoolMap.get(schoolId); for (String courseId : schoolIDToCourseIDMap.get(schoolId)) { GenericEntity c = courseMap.get(courseId); s.appendToList(Constants.ATTR_COURSES, c); } } return new ArrayList<GenericEntity>(schoolMap.values()); } /** * Get the associations between courses and sections */ @Override public List<GenericEntity> getCourseSectionMappings(List<GenericEntity> sections, String schoolId, String token) { Map<String, GenericEntity> courseMap = new HashMap<String, GenericEntity>(); Map<String, String> sectionIDToCourseIDMap = new HashMap<String, String>(); // this temporary sectionLookup will be used for cross reference between // courseId and // section. Map<String, Set<GenericEntity>> sectionLookup = new HashMap<String, Set<GenericEntity>>(); // iterate each section if (sections != null) { Map<String, String> courseOfferingToCourseIDMap = new HashMap<String, String>(); String url = ""; if (schoolId != null) { url = SDKConstants.SCHOOLS_ENTITY + schoolId; } // find the course for each course offering List<GenericEntity> courseOfferings = readEntityList(token, url + SDKConstants.COURSE_OFFERINGS + "?" + this.buildQueryString(null)); if (courseOfferings != null) { for (GenericEntity courseOffering : courseOfferings) { // Get course using courseId reference in section String courseOfferingId = (String) courseOffering.get(Constants.ATTR_ID); String courseId = (String) courseOffering.get(Constants.ATTR_COURSE_ID); courseOfferingToCourseIDMap.put(courseOfferingId, courseId); } } for (GenericEntity section : sections) { // Get course using courseId reference in section String courseOfferingId = (String) section.get(Constants.ATTR_COURSE_OFFERING_ID); String courseId = courseOfferingToCourseIDMap.get(courseOfferingId); if (!sectionLookup.containsKey(courseId)) { sectionLookup.put(courseId, new TreeSet<GenericEntity>(new GenericEntityComparator( Constants.ATTR_SECTION_NAME, String.class))); } sectionLookup.get(courseId).add(section); } // get course Entity List<GenericEntity> courses = readEntityList(token, url + SDKConstants.COURSES_ENTITY + "?" + this.buildQueryString(null)); // update courseMap with courseId. "id" for this entity for (GenericEntity course : courses) { // Add course to courseMap // courseMap.put(course.getId(), course); Set<GenericEntity> matchedSections = sectionLookup.get(course.getId()); if (matchedSections != null) { // Add course to courseMap courseMap.put(course.getId(), course); Iterator<GenericEntity> sectionEntities = matchedSections.iterator(); while (sectionEntities.hasNext()) { GenericEntity sectionEntity = sectionEntities.next(); course.appendToList(Constants.ATTR_SECTIONS, sectionEntity); // update sectionIdToCourseIdMap sectionIDToCourseIDMap.put(sectionEntity.getId(), course.getId()); } } } } List<GenericEntity> courses = new ArrayList<GenericEntity>(courseMap.values()); Collections.sort(courses, new GenericEntityComparator(Constants.ATTR_COURSE_TITLE, String.class)); return courses; } /** * Get the associations between courses and sections */ private void getCourseSectionsMappings(List<GenericEntity> sections, String token, Map<String, GenericEntity> courseMap, Map<String, String> sectionIDToCourseIDMap) { // this variable is used to prevent sending duplicate courseId to API Set<String> courseIdTracker = new HashSet<String>(); // this temporary sectionLookup will be used for cross reference between // courseId and // section. Map<String, Set<GenericEntity>> sectionLookup = new HashMap<String, Set<GenericEntity>>(); List<String> courseIds = new ArrayList<String>(); // iterate each section if (sections != null) { for (GenericEntity section : sections) { // Get course using courseId reference in section String courseId = (String) section.get(Constants.ATTR_COURSE_ID); // search course which doesn't exist already if (!courseMap.containsKey(courseId)) { if (!courseIdTracker.contains(courseId)) { courseIds.add(courseId); courseIdTracker.add(courseId); } if (!sectionLookup.containsKey(courseId)) { sectionLookup.put(courseId, new HashSet<GenericEntity>()); } sectionLookup.get(courseId).add(section); } } } // get Entities by given courseIds if (courseIds.size() > 0) { // get course Entities List<GenericEntity> courses = getCourses(token, courseIds, null); Collections.sort(courses, new Comparator<GenericEntity>() { @Override public int compare(GenericEntity o1, GenericEntity o2) { return o1.getString("coursesName").compareTo(o2.getString("coursesName")); } }); // update courseMap with courseId. "id" for this entity for (GenericEntity course : courses) { // Add course to courseMap courseMap.put(course.getId(), course); Set<GenericEntity> matchedSections = sectionLookup.get(course.getId()); if (matchedSections != null) { Iterator<GenericEntity> sectionEntities = matchedSections.iterator(); while (sectionEntities.hasNext()) { GenericEntity sectionEntity = sectionEntities.next(); course.appendToList(Constants.ATTR_SECTIONS, sectionEntity); // update sectionIdToCourseIdMap sectionIDToCourseIDMap.put(sectionEntity.getId(), course.getId()); } } } } } /** * Get the associations between schools and sections */ private void getSchoolSectionsMappings(List<GenericEntity> sections, String token, List<GenericEntity> schools, Map<String, GenericEntity> schoolMap, Map<String, String> sectionIDToSchoolIDMap) { // temporary cross reference between schoolId and sections Map<String, Set<GenericEntity>> sectionLookup = new HashMap<String, Set<GenericEntity>>(); // iterate each section if (sections != null) { for (GenericEntity section : sections) { String schoolId = (String) section.get(Constants.ATTR_SCHOOL_ID); // search school which doesn't exist already if (!schoolMap.containsKey(schoolId)) { if (!sectionLookup.containsKey(schoolId)) { sectionLookup.put(schoolId, new HashSet<GenericEntity>()); } sectionLookup.get(schoolId).add(section); } } } if (schools != null) { // update schoolMap with schoolId. "id" for this entity for (GenericEntity school : schools) { String schoolId = school.getId(); Set<GenericEntity> matchedSections = sectionLookup.get(schoolId); if (matchedSections != null) { for (GenericEntity sectionEntity : matchedSections) { // Add school to schoolmap schoolMap.put(school.getId(), school); // update sectionIdToSchoolIdMap sectionIDToSchoolIDMap.put(sectionEntity.getId(), schoolId); } } } } } private void ensureSectionName(GenericEntity section) { if ((section != null) && (section.get(Constants.ATTR_SECTION_NAME) == null)) { section.put(Constants.ATTR_SECTION_NAME, section.get(Constants.ATTR_UNIQUE_SECTION_CODE)); } } /** * Builds a comma-separated string from the given string item list * * @param items * @return */ private String buildListString(List<String> items) { return (items == null) ? "" : StringUtils.join(items, ","); } /** * Builds a query string from the given parameter map * * @param params * @return */ private String buildQueryString(Map<String, String> params) { StringBuilder query = new StringBuilder(); String separator = ""; // Setup defaults including paging disabled if (params == null) { params = new HashMap<String, String>(); } if (!params.containsKey(Constants.LIMIT)) { params.put(Constants.LIMIT, String.valueOf(Constants.MAX_RESULTS)); } for (Map.Entry<String, String> e : params.entrySet()) { query.append(separator); separator = "&"; query.append(e.getKey()); query.append("="); query.append(e.getValue()); } return query.toString(); } public SLIClient getClient(String sessionToken) throws OAuthException, MalformedURLException, URISyntaxException { return clientFactory.getClientWithSessionToken(sessionToken); } }