/* * 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.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import com.google.gson.Gson; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slc.sli.dashboard.entity.ConfigMap; import org.slc.sli.dashboard.entity.GenericEntity; 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.ExecutionTimeLogger.LogExecutionTime; import org.slc.sli.dashboard.util.JsonConverter; import org.slc.sli.dashboard.util.SecurityUtil; /** * * API Client class used by the Dashboard to make calls to the API service. * TODO: Refactor public methods to private and mock with PowerMockito in unit * tests * * @author svankina * */ public class LiveAPIClient { private static final Logger LOGGER = LoggerFactory.getLogger(LiveAPIClient.class); // base urls private static final String STAFF_URL = "/v1/staff/"; private static final String EDORGS_URL = "/v1/educationOrganizations/"; private static final String SCHOOLS_URL = "/v1/schools"; private static final String COURSES_URL = "/v1/courses/"; private static final String COURSE_OFFERINGS_URL = "/v1/courseOfferings/"; private static final String SECTIONS_URL = "/v1/sections/"; private static final String STUDENTS_URL = "/v1/students/"; private static final String TEACHERS_URL = "/v1/teachers/"; private static final String HOME_URL = "/v1/home/"; private static final String ASSMT_URL = "/v1/assessments/"; private static final String SESSION_URL = "/v1/sessions/"; private static final String STUDENT_ASSESSMENTS_URL = "/v1/studentAssessments/"; private static final String STUDENT_GRADEBOOK_ENTR_URL = "/v1/studentGradebookEntries"; private static final String STUDENT_ACADEMIC_RECORD_URL = "/v1/studentAcademicRecords"; // resources to append to base urls private static final String STAFF_EDORG_ASSOC = "/staffEducationOrgAssignmentAssociations/educationOrganizations"; private static final String ATTENDANCES = "/attendances"; private static final String STUDENT_SECTION_ASSOC = "/studentSectionAssociations"; private static final String TEACHER_SECTION_ASSOC = "/teacherSectionAssociations"; private static final String STUDENT_ASSESSMENTS = "/studentAssessments"; private static final String SECTIONS = "/sections"; private static final String STUDENTS = "/students"; private static final String COURSE_TRANSCRIPTS = "/courseTranscripts"; private static final String CUSTOM_DATA = "/custom"; private static final String STUDENT_PARENT_ASSOC = "/studentParentAssociations"; private static final String PARENTS = "/parents"; // link names private static final String ED_ORG_LINK = "getParentEducationOrganization"; private static final String SCHOOL_LINK = "getSchool"; private static final String STUDENT_SCHOOL_ASSOCIATIONS_LINK = "getStudentSchoolAssociations"; // attributes private static final String EDORG_SLI_ID_ATTRIBUTE = "edOrgSliId"; private static final String EDORG_ATTRIBUTE = "edOrg"; private static final String API_VERSION = "v1"; /** * 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; } } private String apiUrl; private RESTClient restClient; private Gson gson; private String gracePeriod; public void setGracePeriod(String gracePeriod) { this.gracePeriod = gracePeriod; } public String getGracePeriod() { return this.gracePeriod; } public LiveAPIClient() { gson = new Gson(); } /** * Get staff information (credentials, etc.) */ //@Override public GenericEntity getStaffInfo(String token) { String id = getId(token); GenericEntity staffEntity = createEntityFromAPI(getApiUrl() + STAFF_URL + id, token); List<GenericEntity> edOrgsList = createEntitiesFromAPI(getApiUrl() + STAFF_URL + id + STAFF_EDORG_ASSOC, token); if ((edOrgsList != null) && (edOrgsList.size() > 0)) { // Processing only first EdOrg for now GenericEntity edOrgEntity = edOrgsList.get(0); if (edOrgEntity != null) { String edOrgSliId = edOrgEntity.getId(); staffEntity.put(EDORG_SLI_ID_ATTRIBUTE, edOrgSliId); staffEntity.put(EDORG_ATTRIBUTE, edOrgEntity); } } return staffEntity; } /** * Get educational organization custom data */ //@Override public ConfigMap getEdOrgCustomData(String token, String id) { CustomEntityWrapper jsonConfig = (CustomEntityWrapper) createEntityFromAPI(getApiUrl() + EDORGS_URL + id + CUSTOM_DATA, token, CustomEntityWrapper.class); return JsonConverter.fromJson(jsonConfig.value, ConfigMap.class); } /** * Put or save educational organization custom data */ //@Override public void putEdOrgCustomData(String token, String id, ConfigMap configMap) { putEntityToAPI(getApiUrl() + EDORGS_URL + id + CUSTOM_DATA, token, new CustomEntityWrapper(JsonConverter.toJson(configMap))); } /** * Creates a generic entity from an API call * * @param url * @param token * @param entityClass * @return the entity */ @LogExecutionTime public Object createEntityFromAPI(String url, String token, Class entityClass) { // DE260 - Logging of possibly sensitive data // LOGGER.info("Querying API: {}", url); String response = restClient.makeJsonRequestWHeaders(url, token); if (response == null) { return null; } Object e = gson.fromJson(response, entityClass); return e; } @LogExecutionTime private <T> void putEntityToAPI(String url, String token, T entity) { restClient.putJsonRequestWHeaders(url, token, gson.toJson(entity)); } //@Override public List<GenericEntity> getSchools(String token, List<String> schoolIds) { List<GenericEntity> schools = null; // get schools schools = createEntitiesFromAPI(getApiUrl() + SCHOOLS_URL + "?" + Constants.LIMIT + "=" + Constants.MAX_RESULTS, token); return schools; } //@Override public List<GenericEntity> getCoursesSectionsForSchool(String token, String schoolId) { // get sections List<GenericEntity> sections = null; if (SecurityUtil.isNotEducator()) { sections = createEntitiesFromAPI(getApiUrl() + SCHOOLS_URL + "/" + schoolId + SECTIONS + "?" + Constants.LIMIT + "=" + Constants.MAX_RESULTS, token); enrichSectionsWithSessionDetails(token, sections); sections = filterCurrentSections(sections, true); } else { // TODO: (sivan) check if a simple /section will work for teachers as well String teacherId = getId(token); sections = getSectionsForTeacher(teacherId, token, schoolId); } // get courses List<GenericEntity> courses = new ArrayList<GenericEntity>(); if (sections != null && !sections.isEmpty()) { courses = getCourseSectionMappings(sections, token); } return courses; } /** * Get the associations between courses and sections */ private List<GenericEntity> getCourseSectionMappings(List<GenericEntity> sections, 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>(); // find the course for each course offering List<GenericEntity> courseOfferings = createEntitiesFromAPI(getApiUrl() + COURSE_OFFERINGS_URL + "?" + Constants.LIMIT + "=" + Constants.MAX_RESULTS, token); 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 HashSet<GenericEntity>()); } sectionLookup.get(courseId).add(section); } // get course Entity List<GenericEntity> courses = createEntitiesFromAPI(getApiUrl() + COURSES_URL + "?" + Constants.LIMIT + "=" + Constants.MAX_RESULTS, token); // 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()); } } } } return new ArrayList<GenericEntity>(courseMap.values()); } /** * Get a list of student objects, given the student ids */ //@Override public List<GenericEntity> getStudents(final String token, Collection<String> ids) { if (ids == null || ids.size() == 0) { return null; } List<GenericEntity> students = new ArrayList<GenericEntity>(); for (String id : ids) { GenericEntity student = getStudent(token, id); if (student != null) { students.add(student); } } return students; } /** * Get a list of student assessment results, given a student id */ //@Override public List<GenericEntity> getStudentAssessments(final String token, String studentId) { // make a call to student-assessments, with the student id List<GenericEntity> responses = createEntitiesFromAPI(getApiUrl() + STUDENTS_URL + studentId + STUDENT_ASSESSMENTS, token); // for each link in the returned list, make the student-assessment call // for the result data List<GenericEntity> studentAssmts = new ArrayList<GenericEntity>(); if (responses != null) { for (GenericEntity studentAssmt : responses) { studentAssmts.add(studentAssmt); } } return studentAssmts; } /** * Get assessment info, given a list of assessment ids */ //@Override public List<GenericEntity> getAssessments(final String token, List<String> assessmentIds) { List<GenericEntity> assmts = new ArrayList<GenericEntity>(); for (String assmtId : assessmentIds) { assmts.add(getAssessment(assmtId, token)); } return assmts; } /** * To retrieve Parent Educational Organizations */ //@Override public List<GenericEntity> getParentEducationalOrganizations(final String token, List<GenericEntity> edOrgs) { StringBuilder parentEducationAgencyReferences = new StringBuilder(); Set<String> edOrgParentIdLookup = new HashSet<String>(); for (GenericEntity edOrg : edOrgs) { String parentEducationAgencyReferenceId = (String) edOrg.get(Constants.ATTR_PARENT_EDORG); // if educationOrganizationParentId returns null, it means edOrg // reaches the top of the // hierarchy. Also check educationOrganizationParentId because we do // not want to send // identical educationOrganizationParentId multiple times. if (parentEducationAgencyReferenceId != null && !parentEducationAgencyReferenceId.isEmpty() && !edOrgParentIdLookup.contains(parentEducationAgencyReferenceId)) { if (parentEducationAgencyReferences.length() != 0) { parentEducationAgencyReferences.append(","); } parentEducationAgencyReferences.append(parentEducationAgencyReferenceId); edOrgParentIdLookup.add(parentEducationAgencyReferenceId); } } // if there is any reference to query, access to API and return // entities. if (parentEducationAgencyReferences.length() != 0) { List<GenericEntity> returnedEdOrgsFromAPI = getEntities(token, Constants.ATTR_ED_ORGS, parentEducationAgencyReferences.toString(), Collections.<String, String>emptyMap()); if (returnedEdOrgsFromAPI != null) { return returnedEdOrgsFromAPI; } } return Collections.emptyList(); } //@Override public GenericEntity getParentEducationalOrganization(final String token, GenericEntity edOrg) { List<String> hrefs = extractLinksFromEntity(edOrg, ED_ORG_LINK); for (String href : hrefs) { // API provides *both* parent and children edOrgs in the // "educationOrganization" link. // So this hack needs be made because api doesn't distinguish // between // parent or children edOrgs. DE105 has been logged to resolve it. // TODO: Remove the if statement once DE105 is resolved. // if (href.contains("?" + Constants.ATTR_PARENT_EDORG + "=")) { // // Links to children edOrgs are queries; ignore them. // continue; // } GenericEntity responses = createEntityFromAPI(href, token); return responses; // there should only be one parent. } // if we reached here the edOrg or school doesn't have a parent return null; } /** * Get one student */ //@Override public GenericEntity getStudent(String token, String id) { return createEntityFromAPI(getApiUrl() + STUDENTS_URL + id, token); } //@Override public List<GenericEntity> getStudents(String token, String sectionId, List<String> studentIds) { return createEntitiesFromAPI(getApiUrl() + SECTIONS_URL + sectionId + STUDENT_SECTION_ASSOC + STUDENTS + "?views=assessments,attendances.1," + Constants.ATTR_TRANSCRIPT + ",gradebook" + "&" + Constants.LIMIT + "=" + Constants.MAX_RESULTS, token); } //@Override public List<GenericEntity> getStudentsWithGradebookEntries(final String token, final String sectionId) { return createEntitiesFromAPI(getApiUrl() + SECTIONS_URL + sectionId + STUDENT_SECTION_ASSOC + STUDENTS + "?views=gradebook" + "&" + Constants.LIMIT + "=" + Constants.MAX_RESULTS, token); } //@Override public GenericEntity getStudentWithOptionalFields(String token, String studentId, List<String> optionalFields) { String optFields = StringUtils.join(optionalFields, ','); String url = getApiUrl() + STUDENTS_URL + studentId + "?views=" + optFields; return createEntityFromAPI(url, token); } /** * Get one section */ public GenericEntity getSection(String id, String token) { if (id == null) { return null; } GenericEntity section = createEntityFromAPI(getApiUrl() + SECTIONS_URL + id, token); if (section == null) { return null; } // if no section name, fill in with section code if (section.get(Constants.ATTR_SECTION_NAME) == null) { section.put(Constants.ATTR_SECTION_NAME, section.get(Constants.ATTR_UNIQUE_SECTION_CODE)); } return section; } //@Override public GenericEntity getSession(String token, String id) { GenericEntity session = null; try { session = createEntityFromAPI(getApiUrl() + SESSION_URL + id, token); // DE260 - Logging of possibly sensitive data // LOGGER.debug("Session: {}", session); } catch (Exception e) { LOGGER.warn("Error occured while getting session", e); session = new GenericEntity(); } return session; } /** * Get one student-assessment association */ private GenericEntity getStudentAssessment(String id, String token) { return createEntityFromAPI(getApiUrl() + STUDENT_ASSESSMENTS_URL + id, token); } /** * Get one assessment */ private GenericEntity getAssessment(String id, String token) { return createEntityFromAPI(getApiUrl() + ASSMT_URL + id, token); } /** * Get the user's unique identifier * * @param token * @return */ public String getId(String token) { // Make a call to the /home uri and retrieve id from there String returnValue = ""; GenericEntity response = createEntityFromAPI(getApiUrl() + HOME_URL, token); if (response == null) { return null; } for (Map link : (List<Map>) (response.get(Constants.ATTR_LINKS))) { if (link.get(Constants.ATTR_REL).equals(Constants.ATTR_SELF)) { returnValue = parseId(link); } } return returnValue; } /** * Given a link in the API response, extract the entity's unique id * * @param link * @return */ private String parseId(Map link) { String returnValue; int index = ((String) (link.get(Constants.ATTR_HREF))).lastIndexOf("/"); returnValue = ((String) (link.get(Constants.ATTR_HREF))).substring(index + 1); return returnValue; } /** * retrieve all sections for tokenId * * @param token * @return */ public List<GenericEntity> getSectionsForNonEducator(String token) { // call https://<IP address>/api/rest/<version>/sections List<GenericEntity> sections = createEntitiesFromAPI(getApiUrl() + SECTIONS_URL + "?" + Constants.LIMIT + "=" + Constants.MAX_RESULTS, token); // Enrich sections with session details enrichSectionsWithSessionDetails(token, sections); sections = filterCurrentSections(sections, true); return sections; } /** * Get a list of sections, given a teacher id */ public List<GenericEntity> getSectionsForTeacher(String id, String token, String schoolId) { List<GenericEntity> sections = createEntitiesFromAPI(getApiUrl() + TEACHERS_URL + id + TEACHER_SECTION_ASSOC + SECTIONS, token); // 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; } // This isn't really filtering, rather just adding section codes to sections with no name sections = filterCurrentSections(sections, false); return sections; } /** * Enrich section entities with session details to be leveraged during filtering * * @param token * @param sections */ private void enrichSectionsWithSessionDetails(String token, List<GenericEntity> sections) { List<GenericEntity> sessions = this.getSessions(token); 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) { // 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) { // if no section name, fill in with section code if (section.get(Constants.ATTR_SECTION_NAME) == null) { section.put(Constants.ATTR_SECTION_NAME, section.get(Constants.ATTR_UNIQUE_SECTION_CODE)); } // 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; } //@Override public List<GenericEntity> getSessions(String token) { String url = getApiUrl() + SESSION_URL + "?" + Constants.LIMIT + "=" + Constants.MAX_RESULTS; try { return createEntitiesFromAPI(url, token); } catch (Exception e) { LOGGER.error(e.toString()); return new ArrayList<GenericEntity>(); } } //@Override public List<GenericEntity> getSessionsByYear(String token, String schoolYear) { String url = getApiUrl() + SESSION_URL + "?schoolYear=" + schoolYear; try { return createEntitiesFromAPI(url, token); } catch (Exception e) { LOGGER.error(e.toString()); return new ArrayList<GenericEntity>(); } } //@Override public GenericEntity getAcademicRecord(String token, Map<String, String> params) { String url = getApiUrl() + STUDENT_ACADEMIC_RECORD_URL; if (params != null && params.size() != 0) { url += "?" + buildQueryString(params); } List<GenericEntity> entities = createEntitiesFromAPI(url, token); if (entities == null || entities.size() == 0) { return null; } return entities.get(0); } /** * Returns the homeroom section for the student * * @param studentId * @param token * @return */ //@Override public GenericEntity getHomeRoomForStudent(String studentId, String token) { String url = getApiUrl() + STUDENTS_URL + studentId + STUDENT_SECTION_ASSOC + "?" + Constants.LIMIT + "=" + Constants.MAX_RESULTS; List<GenericEntity> sectionStudentAssociations = createEntitiesFromAPI(url, token); // If only one section association exists for the student, return the // section as home room if (sectionStudentAssociations.size() == 1) { String sectionId = sectionStudentAssociations.get(0).getString(Constants.ATTR_SECTION_ID); return getSection(sectionId, token); } // If multiple section associations exist for the student, return the // section with // homeroomIndicator set to true for (GenericEntity secStudentAssociation : sectionStudentAssociations) { if ((secStudentAssociation.get(Constants.ATTR_HOMEROOM_INDICATOR) != null) && ((Boolean) secStudentAssociation.get(Constants.ATTR_HOMEROOM_INDICATOR))) { String sectionId = secStudentAssociation.getString(Constants.ATTR_SECTION_ID); return getSection(sectionId, token); } } return null; } /** * Returns the primary staff associated with the section. * * @param sectionId * @param token * @return */ //@Override public GenericEntity getTeacherForSection(String sectionId, String token) { String url = getApiUrl() + SECTIONS_URL + sectionId + TEACHER_SECTION_ASSOC; List<GenericEntity> teacherSectionAssociations = createEntitiesFromAPI(url, token); if (teacherSectionAssociations != null) { for (GenericEntity teacherSectionAssociation : teacherSectionAssociations) { if (teacherSectionAssociation.getString(Constants.ATTR_CLASSROOM_POSITION).equals( Constants.TEACHER_OF_RECORD)) { String teacherUrl = getApiUrl() + TEACHERS_URL + teacherSectionAssociation.getString(Constants.ATTR_TEACHER_ID); GenericEntity teacher = createEntityFromAPI(teacherUrl, token); return teacher; } } } return null; } /** * Simple method to return a list of attendance data. * * @return A list of attendance events for a student. */ //@Override public List<GenericEntity> getStudentAttendance(final String token, String studentId, String start, String end) { String url = STUDENTS_URL + studentId + ATTENDANCES; if (start != null && start.length() > 0) { url += "?eventDate>=" + start; url += "&eventDate<=" + end; } try { List<GenericEntity> ge = createEntitiesFromAPI(getApiUrl() + url, token); if (ge != null) { return ge; } } catch (Exception e) { LOGGER.error("Couldn't retrieve attendance for:" + studentId, e); } return Collections.emptyList(); } private String getUsername() { return SecurityUtil.getPrincipal().getUsername().replace(" ", ""); } /** * Creates a generic entity from an API call * * @param url * @param token * @return the entity */ @ExecutionTimeLogger.LogExecutionTime public GenericEntity createEntityFromAPI(String url, String token) { // DE260 - Logging of possibly sensitive data // LOGGER.info("Querying API: {}", url); String response = restClient.makeJsonRequestWHeaders(url, token); if (response == null) { return null; } GenericEntity e = gson.fromJson(response, GenericEntity.class); return e; } /** * Retrieves an entity list from the specified API url * and instantiates from its JSON representation * * @param url * - the API url to retrieve the entity list JSON string * representation * @param token * - the principle authentication token * * @return entityList * - the generic entity list */ @ExecutionTimeLogger.LogExecutionTime public List<GenericEntity> createEntitiesFromAPI(String url, String token) { List<GenericEntity> entityList = new ArrayList<GenericEntity>(); // DE260 - Logging of possibly sensitive data // Parse JSON // LOGGER.info("Querying API for list: {}", url); String response = restClient.makeJsonRequestWHeaders(url, token); if (response == null) { return Collections.emptyList(); } List<Map> maps = null; // this method expected to return JSON in array. // If JSON is not in array, convert to array. if (!response.startsWith("[")) { maps = gson.fromJson("[" + response + "]", new ArrayList<Map>().getClass()); } else { maps = gson.fromJson(response, new ArrayList<Map>().getClass()); } if (maps != null) { for (Map<String, Object> map : maps) { entityList.add(new GenericEntity(map)); } } else { maps = Collections.emptyList(); } return entityList; } /** * Returns a list of courses for a given student and query params * i.e students/{studentId}/studentCourseAssociations/courses?subejctArea= * "math"&includeFields= * courseId,courseTitle * * @param token * Securiy token * @param sectionId * The student Id * @param params * Query params * @return */ //@Override public List<GenericEntity> getCourses(final String token, final String sectionId, Map<String, String> params) { // get the entities return createEntitiesFromAPI(getApiUrl() + SECTIONS_URL + sectionId + STUDENT_SECTION_ASSOC + STUDENTS + "?views=transcript", token); } /** * Returns a list of student course associations for a * given student and query params * i.e students/{studentId}/studentCourseAssociations?courseId={courseId}& * includeFields= * finalLettergrade,studentId * * @param token * Securiy token * @param studentId * The student Id * @param params * Query params * @return */ //@Override public List<GenericEntity> getCourseTranscripts(final String token, final String studentId, Map<String, String> params) { // get the entities List<GenericEntity> entities = createEntitiesFromAPI( buildStudentURI(studentId, COURSE_TRANSCRIPTS, params), token); return entities; } /** * Returns an entity for the given type, id and params * * @param token * Security token * @param type * Type of the entity * @param id * The id of the entity * @param params * param map * @return */ //@Override public List<GenericEntity> getEntities(final String token, final String type, final String id, Map<String, String> params) { StringBuilder url = new StringBuilder(); // build the url url.append(getApiUrl()); url.append("/" + API_VERSION + "/" + type + "/" + id); // add the query string if (!params.isEmpty()) { url.append("?"); url.append(buildQueryString(params)); url.append("&"); } else { url.append("?"); } url.append(Constants.LIMIT + "=" + Constants.MAX_RESULTS); return createEntitiesFromAPI(url.toString(), token); } /** * Returns an entity for the given type, id and params * * @param token * Security token * @param type * Type of the entity * @param id * The id of the entity * @param params * param map * @return */ //@Override public GenericEntity getEntity(final String token, final String type, final String id, Map<String, String> params) { StringBuilder url = new StringBuilder(); // build the url url.append(getApiUrl()); url.append("/" + API_VERSION + "/"); url.append(type); if (id != null) { url.append("/"); url.append(id); } // add the query string if (params != null && !params.isEmpty()) { url.append("?"); url.append(buildQueryString(params)); } return createEntityFromAPI(url.toString(), token); } /** * Returns a list of sections for the given student and params * * @param token * @param studentId * @param params * @return */ //@Override public List<GenericEntity> getSections(final String token, final String studentId, Map<String, String> params) { // get the entities List<GenericEntity> entities = createEntitiesFromAPI( buildStudentURI(studentId, STUDENT_SECTION_ASSOC + SECTIONS , params), token); return entities; } /* * Retrieves and returns student school associations for a given student. */ //@Override public List<GenericEntity> getStudentEnrollment(final String token, GenericEntity student) { List<String> urls = extractLinksFromEntity(student, STUDENT_SCHOOL_ASSOCIATIONS_LINK); if (urls == null || urls.isEmpty()) { return new LinkedList<GenericEntity>(); } // Retrieve the student school associations from the first link with // STUDENT_SCHOOL_ASSOCIATIONS_LINK // sorted by entryDate String url = this.sortBy(urls.get(0), "entryDate", "descending") + "&" + Constants.LIMIT + "=" + Constants.MAX_RESULTS; List<GenericEntity> studentSchoolAssociations = createEntitiesFromAPI(url, token); for (GenericEntity studentSchoolAssociation : studentSchoolAssociations) { studentSchoolAssociation = GenericEntityEnhancer.enhanceStudentSchoolAssociation(studentSchoolAssociation); String schoolUrl = extractLinksFromEntity(studentSchoolAssociation, SCHOOL_LINK).get(0); // Retrieve the school for the corresponding student school // association GenericEntity school = createEntityFromAPI(schoolUrl, token); studentSchoolAssociation.put(Constants.ATTR_SCHOOL, school); } return studentSchoolAssociations; } /** * Returns a list of student grade book entries for a given student and * params * * @param token * Security token * @param studentId * The student Id * @param params * param map * @return */ //@Override public List<GenericEntity> getStudentGradebookEntries(final String token, final String studentId, Map<String, String> params) { StringBuilder url = new StringBuilder(); // add the studentId to the param list params.put(Constants.ATTR_STUDENT_ID, studentId); // build the url url.append(getApiUrl()); url.append(STUDENT_GRADEBOOK_ENTR_URL); // add the query string if (!params.isEmpty()) { url.append("?"); url.append(buildQueryString(params)); // url.append("&limit=" + Constants.MAX_RESULTS); } // get the entities List<GenericEntity> entities = createEntitiesFromAPI(url.toString(), token); return entities; } /** * Builds a student based URI using the given studentId,path and param map * * @param studentId * The studentId * @param path * The URI path * @param params * The param map * @return */ protected String buildStudentURI(final String studentId, String path, Map<String, String> params) { StringBuilder url = new StringBuilder(); // build the url url.append(getApiUrl()); url.append(STUDENTS_URL); url.append(studentId); url.append(path); // add the query string if (!params.isEmpty()) { url.append("?"); url.append(buildQueryString(params)); // url.append("&limit=" + Constants.MAX_RESULTS); } return url.toString(); } /** * Builds a query string from the given param map * * @param params * The param map * @return */ protected String buildQueryString(Map<String, String> params) { StringBuilder query = new StringBuilder(); String separator = ""; 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(); } /** * Extract the link with the given relationship from an entity */ private static List<String> extractLinksFromEntity(GenericEntity e, String rel) { List<String> retVal = new ArrayList<String>(); if (e == null || !e.containsKey(Constants.ATTR_LINKS)) { return retVal; } for (Map link : (List<Map>) (e.get(Constants.ATTR_LINKS))) { if (link.get(Constants.ATTR_REL).equals(rel)) { String href = (String) link.get(Constants.ATTR_HREF); retVal.add(href); } } return retVal; } /** * Getter and Setter used by Spring to instantiate the live/test api class * * @return */ public RESTClient getRestClient() { return restClient; } public void setRestClient(RESTClient restClient) { this.restClient = restClient; } public String getApiUrl() { return apiUrl + Constants.API_PREFIX; } public void setApiUrl(String apiUrl) { this.apiUrl = apiUrl; } //@Override public List<GenericEntity> getStudentsWithSearch(String token, String firstName, String lastName) { // TODO Auto-generated method stub String queryString = "?"; boolean queryExists = false; if (firstName != null && !firstName.equals("")) { queryString += "name.firstName=" + firstName; queryExists = true; if (lastName != null && !lastName.equals("")) { queryString += "&name.lastSurname=" + lastName; } } else { if (lastName != null && !lastName.equals("")) { queryExists = true; queryString += "name.lastSurname=" + lastName; } } String url = getApiUrl() + STUDENTS_URL; if (queryExists) { // &limit=0 is the API syntax to return all results, not just the first 50 as per default url += queryString + "&" + Constants.LIMIT + "=" + Constants.MAX_RESULTS; } return createEntitiesFromAPI(url, token); } /** * Return a url with the sortBy parameter * * @param url * @param sortBy * @return */ //@Override public String sortBy(String url, String sortBy) { return url + "?sortBy=" + sortBy; }; /** * Return a url with the sortBy and sortOrder parameter * * @param url * @param sortBy * @param sortOrder * "descending" or "ascending" * @return */ //@Override public String sortBy(String url, String sortBy, String sortOrder) { return url + "?sortBy=" + sortBy + "&sortOrder=" + sortOrder; } //@Override public List<GenericEntity> getParentsForStudent(String token, String studentId) { return createEntitiesFromAPI( buildStudentURI(studentId, STUDENT_PARENT_ASSOC + PARENTS, Collections.<String, String>emptyMap()), token); } }