/*
* 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.manager.impl;
import java.text.DateFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
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.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.commons.lang.WordUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.slc.sli.dashboard.entity.Config;
import org.slc.sli.dashboard.entity.Config.Data;
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.manager.ApiClientManager;
import org.slc.sli.dashboard.manager.EntityManager;
import org.slc.sli.dashboard.manager.PopulationManager;
import org.slc.sli.dashboard.util.CacheableUserData;
import org.slc.sli.dashboard.util.Constants;
import org.slc.sli.dashboard.util.TimedLogic;
/**
* Facilitates creation of logical aggregations of EdFi entities/associations
* such as a student summary comprised of student profile,
* program, and assessment information in order to deliver the
* Population Summary interaction.
*
* @author Robert Bloh
*
*/
public class PopulationManagerImpl extends ApiClientManager implements PopulationManager {
private static final String ATTENDANCE_TARDY = "Tardy";
private static final String ATTENDANCE_ABSENCE = "Absence";
private static final int DEFAULT_YEARS_BACK = 3;
private static final int NO_LIMIT = -1;
private static Logger log = LoggerFactory.getLogger(PopulationManagerImpl.class);
@Autowired
private EntityManager entityManager;
public PopulationManagerImpl() {
}
/*
* (non-Javadoc)
*
* @see
* org.slc.sli.dashboard.manager.PopulationManagerI#getAssessments(java.lang.String,
* java.util.List)
*/
@Override
public List<GenericEntity> getAssessments(String token, List<GenericEntity> studentSummaries) {
Set<GenericEntity> assessments = new TreeSet<GenericEntity>(new Comparator<GenericEntity>() {
@Override
public int compare(GenericEntity att1, GenericEntity att2) {
return (att2.getString("id")).compareTo(att1.getString("id"));
}
});
for (GenericEntity studentSummary : studentSummaries) {
List<Map<String, Object>> studentAssessments = (List<Map<String, Object>>) studentSummary
.get(Constants.ATTR_STUDENT_ASSESSMENTS);
for (Map<String, Object> studentAssessment : studentAssessments) {
try {
GenericEntity assessment = new GenericEntity((Map) studentAssessment.get("assessments"));
assessments.add(assessment);
} catch (ClassCastException cce) {
log.warn(cce.getMessage());
}
}
}
return new ArrayList<GenericEntity>(assessments);
}
/*
* (non-Javadoc)
*
* @see
* org.slc.sli.dashboard.manager.PopulationManagerI#getStudentSummaries(java.lang.
* String, java.util.List, org.slc.sli.config.ViewConfig, java.lang.String,
* java.lang.String)
*/
@Override
public List<GenericEntity> getStudentSummaries(String token, List<String> studentIds, String sessionId,
String sectionId) {
long startTime = System.nanoTime();
// Initialize student summaries
List<GenericEntity> studentSummaries = entityManager.getStudents(token, sectionId);
log.warn("@@@@@@@@@@@@@@@@@@ Benchmark for student section view: {}", (System.nanoTime() - startTime) * 1.0e-9);
return studentSummaries;
}
/*
* (non-Javadoc)
*
* @see
* org.slc.sli.dashboard.manager.PopulationManagerI#getListOfStudents(java.lang.String
* , java.lang.Object, org.slc.sli.dashboard.entity.Config.Data)
*/
@Override
@CacheableUserData
public GenericEntity getListOfStudents(String token, Object sectionId, Config.Data config) {
String id = (String) sectionId;
// get student summary data
List<GenericEntity> studentSummaries = getStudentSummaries(token, null, null, id);
// apply assmt filters and flatten assmt data structure for easy
// fetching
applyAssessmentFilters(studentSummaries, config);
// data enhancements
enhanceListOfStudents(studentSummaries, id);
GenericEntity result = new GenericEntity();
Collections.sort(studentSummaries, STUDENT_COMPARATOR);
result.put(Constants.ATTR_STUDENTS, studentSummaries);
return result;
}
/**
* Make enhancements that make it easier for front-end javascript to use the
* data
*
* @param studentSummaries
*/
public void enhanceListOfStudents(List<GenericEntity> studentSummaries, String sectionId) {
if (studentSummaries != null) {
for (GenericEntity student : studentSummaries) {
if (student == null) {
continue;
}
// clean out some unneeded gunk
scrubStudentData(student);
// add full name
addFullName(student);
// add the final grade
addFinalGrades(student, sectionId);
// add the grade book
addCurrentSessionGrades(student, sectionId);
// transform assessment score format
transformAssessmentFormat(student);
// tally up attendance data
tallyAttendanceData(student);
}
}
}
/**
* Create an attribute for the full student name (first name + last name)
*
* @param student
*/
public void addFullName(GenericEntity student) {
Map name = (Map) student.get(Constants.ATTR_NAME);
if (name != null) {
String fullName = (String) name.get(Constants.ATTR_FIRST_NAME) + " "
+ (String) name.get(Constants.ATTR_LAST_SURNAME);
name.put(Constants.ATTR_FULL_NAME, fullName);
}
}
/**
* Tally up individual attendance events. Front-end needs to show aggregated
* results.
*
* @param student
*/
public void tallyAttendanceData(GenericEntity student) {
Map<String, Object> attendanceBody = (Map<String, Object>) student.get(Constants.ATTR_STUDENT_ATTENDANCES);
if (attendanceBody != null) {
List<Map<String, Object>> attendances = (List<Map<String, Object>>) attendanceBody
.get(Constants.ATTR_STUDENT_ATTENDANCES);
int absenceCount = 0;
int tardyCount = 0;
if (attendances != null && attendances.size() > 0) {
for (Map attendance : attendances) {
String eventCategory = (String) attendance.get(Constants.ATTR_ATTENDANCE_EVENT_CATEGORY);
if (eventCategory.contains(ATTENDANCE_ABSENCE)) {
absenceCount++;
} else if (eventCategory.contains(ATTENDANCE_TARDY)) {
tardyCount++;
}
}
int attendanceRate = Math
.round((((float) (attendances.size() - absenceCount)) / attendances.size()) * 100);
int tardyRate = Math.round((((float) tardyCount) / attendances.size()) * 100);
attendanceBody.remove(Constants.ATTR_STUDENT_ATTENDANCES);
attendanceBody.put(Constants.ATTR_ABSENCE_COUNT, absenceCount);
attendanceBody.put(Constants.ATTR_TARDY_COUNT, tardyCount);
attendanceBody.put(Constants.ATTR_ATTENDANCE_RATE, attendanceRate);
attendanceBody.put(Constants.ATTR_TARDY_RATE, tardyRate);
}
}
}
/**
* Modify the data structure for assessments, for front-end convenience
*
* @param student
*/
public void transformAssessmentFormat(GenericEntity student) {
Map studentAssmtAssocs = (Map) student.get(Constants.ATTR_ASSESSMENTS);
if (studentAssmtAssocs == null) {
return;
}
Collection<Map> assmtResults = studentAssmtAssocs.values();
for (Map assmtResult : assmtResults) {
if (assmtResult == null) {
continue;
}
// for each score result, create a new attribute that makes the
// score easily accessible without looping through this list
List<Map> scoreResults = (List<Map>) assmtResult.get(Constants.ATTR_SCORE_RESULTS);
if (scoreResults != null) {
for (Map scoreResult : scoreResults) {
String type = (String) scoreResult.get(Constants.ATTR_ASSESSMENT_REPORTING_METHOD);
String result = (String) scoreResult.get(Constants.ATTR_RESULT);
assmtResult.put(type, result);
}
}
// create a new attribute "perfLevel"
List<List<Map>> perfLevelsDescs = (List<List<Map>>) assmtResult
.get(Constants.ATTR_PERFORMANCE_LEVEL_DESCRIPTOR);
if (perfLevelsDescs != null) {
for (List<Map> perfLevelsDesc : perfLevelsDescs) {
if (perfLevelsDesc != null && perfLevelsDesc.size() > 0) {
String perfLevel = (String) perfLevelsDesc.get(0).get(Constants.ATTR_CODE_VALUE);
assmtResult.put(Constants.ATTR_PERF_LEVEL, perfLevel);
}
}
}
}
}
/**
* Clean out some student data, so we don't pass too much unnecessary stuff
* to the front-end
*
* @param student
*/
public void scrubStudentData(GenericEntity student) {
student.remove(Constants.ATTR_LINKS);
}
/**
* Grabs the subject area from the data based on the section ID.
*
* @param stuSectAssocs
* @param sectionId
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private String getSubjectArea(List<Map<String, Object>> stuSectAssocs, String sectionId) {
String subjectArea = null;
for (Map<String, Object> assoc : stuSectAssocs) {
if (sectionId.equalsIgnoreCase((String) assoc.get(Constants.ATTR_SECTION_ID))) {
Map<String, Object> sections = (Map) assoc.get(Constants.ATTR_SECTIONS);
subjectArea = getSubjectArea(sections);
break;
}
}
return subjectArea;
}
/**
* Grabs the Subject Area from a section.
*
* @param sections
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private String getSubjectArea(Map<String, Object> sections) {
if (sections == null) {
return null;
}
Map<String, Object> courses = (Map) sections.get(Constants.ATTR_COURSES);
if (courses == null) {
return null;
}
return (String) courses.get(Constants.ATTR_SUBJECTAREA);
}
/**
*
* Finds the chronologically (strictly) earlier date on the list as compared to the anchorDate.
* Note that this method assumes the dates list has been chronologically sorted, the
* anchorDate is contained on it, and it has no "null" entries. The input parameters themselves
* can be null, in which case null is returned.
*
* @param dates
* @param anchorDate
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private Date findPrevDate(List<Date> dates, Date anchorDate) {
if (anchorDate == null || dates == null) {
return null;
}
Date prevDate = null;
ListIterator<Date> li = dates.listIterator(dates.indexOf(anchorDate));
while (li.hasPrevious()) {
Date d = li.previous();
if (d.before(anchorDate)) {
prevDate = d;
break;
}
}
return prevDate;
}
/**
* Extracts grades from transcriptAssociationRecord based on sections in the
* past. For each section where a transcript with final letter grade
* exist, the grade is added to the list of grades for the semester.
*
* @param student
* @param interSections
* @param stuTransAssocs
* @param curSessionEndDate
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private void addSemesterFinalGrades(GenericEntity student, List<Map<String, Object>> interSections,
List<Map<String, Object>> stuTransAssocs, Date curSessionEndDate) {
// populate the end dates for all the relevant sections
List<Date> sectionDates = new LinkedList<Date>();
sectionDates.add(curSessionEndDate);
for (Map<String, Object> section : interSections) {
List<Date> dates = getSessionDates(section);
Date sectionEndDate = (dates == null) ? null : dates.get(1);
if (sectionEndDate != null) {
sectionDates.add(sectionEndDate);
}
}
Collections.sort(sectionDates);
// find the related sections chronologically preceding the current one on the list
Date prevDate = findPrevDate(sectionDates, curSessionEndDate);
Date prevPrevDate = findPrevDate(sectionDates, prevDate);
// Iterate through the course Id's and grab transcripts grades, once
// we have NUMBER_OF_SEMESTERS transcript grades, we're done
for (Map<String, Object> section : interSections) {
if (section != null) {
Map<String, Object> course = ((Map<String, Object>) section.get(Constants.ATTR_COURSES));
if (course != null) {
String courseId = (String) course.get(Constants.ATTR_ID);
// we need to keep track of special cases, e.g. previous semester and two
// semesters ago
// data
List<Date> dates = getSessionDates(section);
Date sectionEndDate = (dates == null) ? null : dates.get(1);
String tag;
if (sectionEndDate != null) {
if (prevDate != null && prevDate.equals(sectionEndDate)) {
// this is the previous semester's section
tag = "previousSemester";
} else if (prevPrevDate != null && prevPrevDate.equals(sectionEndDate)) {
// this is two semesters ago
tag = "twoSemestersAgo";
} else {
// this is neither of the cases of interest
continue;
}
} else {
// no section end date means we cannot determine where this data belongs
continue;
}
// Find the correct course. If that course is found in
// the transcript, then record that letter grade to the
// semesterScores.
for (Map<String, Object> assoc : stuTransAssocs) {
if (courseId.equalsIgnoreCase((String) assoc.get(Constants.ATTR_COURSE_ID))) {
String finalLetterGrade = (String) assoc.get(Constants.ATTR_FINAL_LETTER_GRADE);
String courseTitle = (String) course.get(Constants.ATTR_COURSE_TITLE);
if (finalLetterGrade != null) {
Map<String, Object> grade = new LinkedHashMap<String, Object>();
grade.put(Constants.SECTION_LETTER_GRADE, finalLetterGrade);
grade.put(Constants.SECTION_COURSE, courseTitle);
List<Map<String, Object>> semesterScores = (List<Map<String, Object>>) student.get(tag);
if (semesterScores == null) {
semesterScores = new ArrayList<Map<String, Object>>();
}
semesterScores.add(grade);
student.put(tag, semesterScores);
break;
}
}
}
}
}
}
}
/**
* This method adds the final grades of a student to the student data. It
* will only grab the latest two grades. Ideally we would filter on subject
* area, but there is currently no subject area data in the SDS.
*
* @param student
*/
@SuppressWarnings({ "unchecked" })
private void addFinalGrades(GenericEntity student, String sectionId) {
try {
Map<String, Object> transcripts = (Map<String, Object>) student.get(Constants.ATTR_TRANSCRIPT);
if (transcripts == null) {
return;
}
/*
* For each student section association, we have to determine if it is in the same
* subject area as the sectionid passed. If it is then we add it to our List.
* Once we have a list of sections. We can grab all of the semester grades
* for those sections whose subject area intersect.
*/
List<Map<String, Object>> stuSectAssocs = (List<Map<String, Object>>) transcripts
.get(Constants.ATTR_STUDENT_SECTION_ASSOC);
if (stuSectAssocs == null) {
return;
}
String subjectArea = getSubjectArea(stuSectAssocs, sectionId);
List<Date> dates = getSessionDates(stuSectAssocs, sectionId);
Date curSessionEndDate = (dates == null) ? null : dates.get(1);
if (curSessionEndDate == null) {
// if we have no current session end date, we cannot determine what the previous
// semesters were
return;
}
List<Map<String, Object>> interSections = new ArrayList<Map<String, Object>>();
for (Map<String, Object> assoc : stuSectAssocs) {
Map<String, Object> sections = (Map<String, Object>) assoc.get(Constants.ATTR_SECTIONS);
// This case will catch if the subjectArea is null
// Add the section only if it's not null
if (sections != null && (subjectArea == null || subjectArea.equalsIgnoreCase(getSubjectArea(sections)))) {
interSections.add(sections);
}
}
addSemesterFinalGrades(student, interSections,
(List<Map<String, Object>>) transcripts.get(Constants.ATTR_COURSE_TRANSCRIPTS), curSessionEndDate);
} catch (ClassCastException ex) {
log.error("Error occured processing Final Grades", ex);
Map<String, Object> grade = new LinkedHashMap<String, Object>();
student.put(Constants.ATTR_SCORE_RESULTS, grade.put(Constants.ATTR_FINAL_LETTER_GRADE, "?"));
} catch (NullPointerException ex) {
log.error("Error occured processing Final Grades", ex);
Map<String, Object> grade = new LinkedHashMap<String, Object>();
student.put(Constants.ATTR_SCORE_RESULTS, grade.put(Constants.ATTR_FINAL_LETTER_GRADE, "?"));
}
}
/**
* Returns the term and the year as a string for a given student Section association.
*
* @param stuSectAssocs
* @param sectionId
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private String getSessionYear(List<Map<String, Object>> stuSectAssocs, String sectionId) {
String sessionString = null;
for (Map<String, Object> assoc : stuSectAssocs) {
if (((String) assoc.get(Constants.ATTR_SECTION_ID)).equalsIgnoreCase(sectionId)) {
Map<String, Object> sections = (Map) assoc.get(Constants.ATTR_SECTIONS);
sessionString = buildSemesterYearString(sections);
break;
}
}
return sessionString;
}
/**
* Extracts the semester+Year from the section passed.
*
* @param sections
* @return (e.g. FallSemester2010-2011 )
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private String buildSemesterYearString(Map<String, Object> section) {
String semesterString = null;
if (section == null) {
return semesterString;
}
Map<String, Object> sessions = (Map) section.get(Constants.ATTR_SESSIONS);
if (sessions == null) {
return semesterString;
}
String term = (String) sessions.get(Constants.ATTR_TERM);
String year = (String) sessions.get(Constants.ATTR_SCHOOL_YEAR);
if (term != null && year != null) {
semesterString = term.replaceAll(" ", "") + year.replaceAll(" ", "");
}
return semesterString;
}
/**
* Extracts the session start and end dates from the specified section.
*
* @param section
* @return session start and end dates or null if information is not available
* @author iivanisevic
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private List<Date> getSessionDates(Map<String, Object> section) {
List<Date> dates;
// get the session for this section
Map<String, Object> session = (Map) section.get(Constants.ATTR_SESSIONS);
if (session == null) {
// no session for this section, bad news
return null;
}
dates = new LinkedList<Date>();
dates.add(parseDate((String) session.get(Constants.ATTR_SESSION_BEGIN_DATE)));
dates.add(parseDate((String) session.get(Constants.ATTR_SESSION_END_DATE)));
return dates;
}
/**
* Extracts the session start and end dates from the specified section and
* studentSectionAssociation.
*
* @param stuSectAssocs
* @param sectionId
* @return session start and end dates or null if information is not available
* @author iivanisevic
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
private List<Date> getSessionDates(List<Map<String, Object>> stuSectAssocs, String sectionId) {
List<Date> dates = null;
for (Map<String, Object> assoc : stuSectAssocs) {
// find the association containing this sectionId
String tempSectionId = (String) assoc.get(Constants.ATTR_SECTION_ID);
if (tempSectionId != null && tempSectionId.equalsIgnoreCase(sectionId)) {
// we found the section we were looking for
Map<String, Object> section = (Map) assoc.get(Constants.ATTR_SECTIONS);
if (section != null) {
dates = getSessionDates(section);
break;
}
}
}
return dates;
}
private Date parseDate(String date) {
final DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
try {
if (date != null) {
return formatter.parse(date);
}
} catch (ParseException e) {
log.error("Error parsing dates. Date string was: " + date);
}
return null;
}
/**
* Adds the current session grades to the student in a easily retrievable manner.
*
* @param student
* @param sectionId
*/
@SuppressWarnings("unchecked")
private void addCurrentSessionGrades(GenericEntity student, String sectionId) {
final DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
// Sort the grades
SortedSet<GenericEntity> sortedList = new TreeSet<GenericEntity>(new Comparator<GenericEntity>() {
@Override
public int compare(GenericEntity a, GenericEntity b) {
Object dateA = a.get(Constants.ATTR_DATE_FULFILLED);
Object dateB = b.get(Constants.ATTR_DATE_FULFILLED);
if (dateA == null) {
return 1;
} else if (dateB == null) {
return -1;
}
return ((Date) dateA).compareTo((Date) dateB);
}
});
// Get the term and year
try {
Map<String, Object> transcripts = (Map<String, Object>) student.get(Constants.ATTR_TRANSCRIPT);
if (transcripts == null) {
return;
}
List<Map<String, Object>> stuSectAssocs = (List<Map<String, Object>>) transcripts
.get(Constants.ATTR_STUDENT_SECTION_ASSOC);
if (stuSectAssocs == null) {
return;
}
// figure out what the start and end dates are for the current session
List<Date> dates = getSessionDates(stuSectAssocs, sectionId);
Date sessionStart = (dates == null) ? null : dates.get(0);
Date sessionEnd = (dates == null) ? null : dates.get(1);
// next we ensure that gradebook entries belong to the current session
// iterate and add to letter grade
List<Map<String, Object>> gradeEntries = (List<Map<String, Object>>) student
.get(Constants.ATTR_STUDENT_GRADEBOOK_ENTRIES);
if (gradeEntries == null) {
return;
}
for (Map<String, Object> currentGrade : gradeEntries) {
GenericEntity gradeDate = new GenericEntity();
// get the grade itself
Object grade = currentGrade.get(Constants.ATTR_LETTER_GRADE_EARNED);
if (grade == null) {
grade = currentGrade.get(Constants.ATTR_NUMERIC_GRADE_EARNED);
if (grade == null) {
continue;
}
}
// when was it earned?
try {
Object dateString = currentGrade.get(Constants.ATTR_DATE_FULFILLED);
// only keep it if it belongs to this session, or there is no date
// associated with it or the current session
if (dateString != null) {
Date date = formatter.parse((String) dateString);
if (((sessionStart == null || sessionStart.compareTo(date) <= 0) && (sessionEnd == null || sessionEnd
.compareTo(date) >= 0))) {
gradeDate.put(Constants.ATTR_DATE_FULFILLED, date);
} else {
// this grade does not belong in the current session
continue;
}
}
} catch (ParseException e) {
log.error("Error parsing dates for a Current Section grade.");
}
gradeDate.put(Constants.ATTR_GRADE_EARNED, grade);
sortedList.add(gradeDate);
}
int count = 0;
for (GenericEntity entity : sortedList) {
student.put("currentSession-" + count++, entity);
}
} catch (ClassCastException ex) {
log.error("Error occured processing Gradebook Entries", ex);
} catch (NullPointerException ex) {
log.error("Error occured processing Gradebook Entries", ex);
}
}
/**
* Find the required assessment results according to the data configuration.
* Filter out the rest.
*/
@SuppressWarnings("unchecked")
public void applyAssessmentFilters(List<GenericEntity> studentSummaries, Config.Data config) {
// Loop through student summaries
if (studentSummaries != null) {
for (GenericEntity summary : studentSummaries) {
// Grab the student's assmt results. Grab assmt filters from config
List<Map<String, Object>> assmtResults = (List<Map<String, Object>>) (summary
.remove(Constants.ATTR_STUDENT_ASSESSMENTS));
Map<String, Object> param = config.getParams();
if (param == null) {
return;
}
Map<String, String> assmtFilters = (Map<String, String>) (config.getParams()
.get(Constants.CONFIG_ASSESSMENT_FILTER));
if (assmtFilters == null) {
return;
}
Map<String, Object> newAssmtResults = new LinkedHashMap<String, Object>();
// Loop through assmt filters
for (String assmtFamily : assmtFilters.keySet()) {
String timeSlotStr = assmtFilters.get(assmtFamily);
if (timeSlotStr != null) {
TimedLogic.TimeSlot timeSlot = TimedLogic.TimeSlot.valueOf(timeSlotStr);
// Apply filter. Add result to student summary.
Map assmt = applyAssessmentFilter(assmtResults, assmtFamily, timeSlot);
// since we flip data to be the property name, we cannot allow dots in it
newAssmtResults.put(assmtFamily.replace('.', '_'), assmt);
}
}
summary.put(Constants.ATTR_ASSESSMENTS, newAssmtResults);
}
}
}
@SuppressWarnings("unchecked")
private List<Map<String, Object>> filterAssessmentByFamily(List<?> assmtResults, String assmtFamily) {
// filter by assmt family name
List<Map<String, Object>> studentAssessmentFiltered = new ArrayList<Map<String, Object>>();
Map<String, Object> assmtResult, assessments;
String family;
for (Object assmtResultObj : assmtResults) {
assmtResult = (Map<String, Object>) assmtResultObj;
assessments = (Map<String, Object>) assmtResult.get(Constants.ATTR_ASSESSMENTS);
if (assessments != null) {
family = (String) assessments.get(Constants.ATTR_ASSESSMENT_FAMILY_HIERARCHY_NAME);
if (family != null && family.contains(assmtFamily)) {
studentAssessmentFiltered.add(assmtResult);
}
}
}
return studentAssessmentFiltered;
}
/**
* Filter a list of assessment results, based on the assessment family and
* timed logic
*
* @param assmtResults
* @param assmtFamily
* @param timedLogic
* @return
*/
private Map applyAssessmentFilter(List<Map<String, Object>> assmtResults, String assmtFamily,
TimedLogic.TimeSlot timeSlot) {
// filter by assmt family name
List<Map<String, Object>> studentAssessmentFiltered = filterAssessmentByFamily(assmtResults, assmtFamily);
if (studentAssessmentFiltered.size() == 0) {
return null;
}
Map chosenAssessment = null;
String objAssmtCode = "";
// call timeslot logic to pick out the assessment we want
switch (timeSlot) {
case MOST_RECENT_RESULT:
chosenAssessment = TimedLogic.getMostRecentAssessment(studentAssessmentFiltered);
break;
case HIGHEST_EVER:
if (!objAssmtCode.equals("")) {
chosenAssessment = TimedLogic.getHighestEverObjAssmt(studentAssessmentFiltered, objAssmtCode);
} else {
chosenAssessment = TimedLogic.getHighestEverAssessment(studentAssessmentFiltered);
}
break;
case MOST_RECENT_WINDOW:
List<Map<String, Object>> assessmentMetaData = new ArrayList<Map<String, Object>>();
// TODO: get the assessment meta data
/*
* Set<String> assessmentIds = new HashSet<String>(); for (Map
* studentAssessment : studentAssessmentFiltered) { String
* assessmentId = (String)
* studentAssessment.get(Constants.ATTR_ASSESSMENT_ID); if
* (!assessmentIds.contains(assessmentId)) { GenericEntity
* assessment = metaDataResolver.getAssmtById(assessmentId);
* assessmentMetaData.add(assessment);
* assessmentIds.add(assessmentId); } }
*/
chosenAssessment = TimedLogic.getMostRecentAssessmentWindow(studentAssessmentFiltered,
assessmentMetaData);
break;
default:
chosenAssessment = TimedLogic.getMostRecentAssessment(studentAssessmentFiltered);
break;
}
return chosenAssessment;
}
private List<GenericEntity> getStudentAttendance(String token, String studentId, String startDate, String endDate) {
List<GenericEntity> list = entityManager.getAttendance(token, studentId, startDate, endDate);
if (list == null) {
return Collections.emptyList();
}
return list;
}
/*
* (non-Javadoc)
*
* @see
* org.slc.sli.dashboard.manager.PopulationManagerI#setEntityManager(org.slc.sli.dashboard.manager
* .EntityManager)
*/
@Override
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
/*
* (non-Javadoc)
*
* @see org.slc.sli.dashboard.manager.PopulationManagerI#getStudent(java.lang.String,
* java.lang.String)
*/
@Override
public GenericEntity getStudent(String token, String studentId) {
return entityManager.getStudent(token, studentId);
}
@Override
public GenericEntity getTeacher(String token, Object teacherId, Config.Data config) {
return getApiClient().getTeacherWithSections(token, (String) teacherId);
}
@Override
public GenericEntity getTeachersForSchool(String token, Object schoolId, Config.Data config) {
List<GenericEntity> teachers = getApiClient().getTeachersForSchool(token, (String) schoolId);
if (teachers != null) {
for (GenericEntity teacher : teachers) {
addFullName(teacher);
}
}
GenericEntity result = new GenericEntity();
result.put(Constants.ATTR_TEACHERS, teachers);
return result;
}
/*
* (non-Javadoc)
*
* @see org.slc.sli.dashboard.manager.PopulationManagerI#getStudent(java.lang.String,
* java.lang.Object, org.slc.sli.dashboard.entity.Config.Data)
*/
@Override
public GenericEntity getStudent(String token, Object studentId, Config.Data config) {
return entityManager.getStudentForCSIPanel(token, (String) studentId);
}
/*
* (non-Javadoc)
*
* @see
* org.slc.sli.dashboard.manager.PopulationManagerI#getAttendance(java.lang.String,
* java.lang.Object, org.slc.sli.dashboard.entity.Config.Data)
*/
@Override
public GenericEntity getAttendance(String token, Object studentIdObj, Config.Data config) {
// get yearsBack from param
String yearsBack = config.getParams() == null ? null : (String) config.getParams().get("yearsBack");
int intYearsBack = DEFAULT_YEARS_BACK;
if (yearsBack != null) {
try {
intYearsBack = Integer.parseInt(yearsBack);
} catch (Exception e) {
log.error("params: value of yearsBack was not integer. [" + intYearsBack + "]. Using default value ["
+ DEFAULT_YEARS_BACK + "]");
intYearsBack = DEFAULT_YEARS_BACK;
}
}
GenericEntity attendance = new GenericEntity();
String studentId = (String) studentIdObj;
// get Enrollment History
List<GenericEntity> enrollments = getApiClient().getEnrollmentForStudent(token, studentId);
// creating lookup index for enrollment, key is term (yyyy-yyyy)
int currentSchoolYear = 0;
Map<String, LinkedHashMap<String, Object>> enrollmentsIndex = new HashMap<String, LinkedHashMap<String, Object>>();
for (LinkedHashMap<String, Object> enrollment : enrollments) {
String entryDateYear = "";
String exitWithdrawDateYear = "";
String entryDate = (String) enrollment.get(Constants.ATTR_ENROLLMENT_ENTRY_DATE);
String exitWithdrawDate = (String) enrollment.get(Constants.ATTR_ENROLLMENT_EXIT_WITHDRAW_DATE);
// find a year for entryDate
if (entryDate != null && entryDate.length() > 3) {
entryDateYear = entryDate.substring(0, 4);
}
// find a year for exitWithdarwDate
if (exitWithdrawDate != null && exitWithdrawDate.length() > 3) {
exitWithdrawDateYear = exitWithdrawDate.substring(0, 4);
} else {
// exitWithdrawDate is null because it is in current term.
// add one year to entryDateYear.
currentSchoolYear = Integer.parseInt(entryDateYear);
exitWithdrawDateYear = Integer.toString(currentSchoolYear + 1);
}
// creating index lookup key
String key = entryDateYear + "-" + exitWithdrawDateYear;
enrollmentsIndex.put(key, enrollment);
}
// Numberformat for %Present - no fraction
NumberFormat numberFormat = NumberFormat.getNumberInstance();
numberFormat.setMaximumFractionDigits(0);
// get attendance for the student
List<GenericEntity> attendanceList = this.getStudentAttendance(token, studentId, null, null);
for (Map<String, Object> targetAttendance : attendanceList) {
// get schoolYearAttendance
List<Map<String, Object>> schoolYearAttendances = (List<Map<String, Object>>) targetAttendance
.get(Constants.ATTR_ATTENDANCE_SCHOOLYEAR_ATTENDANCE);
if (schoolYearAttendances != null) {
// sort by schoolYear
GenericEntityComparator comparator = new GenericEntityComparator(Constants.ATTR_SCHOOL_YEAR,
String.class);
Collections.sort(schoolYearAttendances, Collections.reverseOrder(comparator));
for (Map<String, Object> schoolYearAttendance : schoolYearAttendances) {
int inAttendanceCount = 0;
int absenceCount = 0;
int excusedAbsenceCount = 0;
int unexcusedAbsenceCount = 0;
int tardyCount = 0;
int earlyDepartureCount = 0;
int totalCount = 0;
// get schoolYear
String schoolYear = (String) schoolYearAttendance.get(Constants.ATTR_SCHOOL_YEAR);
// if some reasons we cannot find currentSchoolYear, then display all histories
// if intYearsBack is not set to NO_LIMIT (-1) and found currentSchoolYear,
// then exam whether current loop is within user defined yearsBack
if (intYearsBack != NO_LIMIT && currentSchoolYear != 0) {
int targetYear = Integer.parseInt(schoolYear.substring(0, 4));
// if yearsBack is 1, it means current schoolYear.
// break from the loop if currentSchoolYear-targetYear is over yearsBack.
if ((currentSchoolYear - targetYear) >= intYearsBack) {
break;
}
}
// get attendanceEvent
List<LinkedHashMap<String, Object>> attendanceEvents = (List<LinkedHashMap<String, Object>>) schoolYearAttendance
.get(Constants.ATTR_ATTENDANCE_ATTENDANCE_EVENT);
// count each attendance event
if (attendanceEvents != null) {
for (Map<String, Object> attendanceEvent : attendanceEvents) {
String event = (String) attendanceEvent.get(Constants.ATTR_ATTENDANCE_EVENT_CATEGORY);
if (event != null) {
totalCount++;
if (event.equals(Constants.ATTR_ATTENDANCE_IN_ATTENDANCE)) {
inAttendanceCount++;
} else if (event.equals(Constants.ATTR_ATTENDANCE_ABSENCE)) {
absenceCount++;
} else if (event.equals(Constants.ATTR_ATTENDANCE_EXCUSED_ABSENCE)) {
excusedAbsenceCount++;
} else if (event.equals(Constants.ATTR_ATTENDANCE_UNEXCUSED_ABSENCE)) {
unexcusedAbsenceCount++;
} else if (event.equals(Constants.ATTR_ATTENDANCE_TARDY)) {
tardyCount++;
} else if (event.equals(Constants.ATTR_ATTENDANCE_EARLY_DEPARTURE)) {
earlyDepartureCount++;
}
}
}
}
// get target school year enrollment
LinkedHashMap<String, Object> enrollment = enrollmentsIndex.get(schoolYear);
GenericEntity currentTermAttendance = new GenericEntity();
// set school term
currentTermAttendance.put(Constants.ATTENDANCE_HISTORY_TERM, schoolYear);
String nameOfInstitution = "";
// get school name from enrollment
if (enrollment != null) {
Map<String, Object> school = (Map<String, Object>) enrollment.get(Constants.ATTR_SCHOOL);
if (school != null) {
nameOfInstitution = (String) school.get(Constants.ATTR_NAME_OF_INST);
}
}
// set school name
currentTermAttendance.put(Constants.ATTENDANCE_HISTORY_SCHOOL, nameOfInstitution);
String gradeLevel = "";
// set grade level
if (enrollment != null) {
gradeLevel = (String) enrollment.get(Constants.ATTR_ENROLLMENT_ENTRY_GRADE_LEVEL_CODE);
}
currentTermAttendance.put(Constants.ATTENDANCE_HISTORY_GRADE_LEVEL, gradeLevel);
// set %Present
currentTermAttendance
.put(Constants.ATTENDANCE_HISTORY_PRESENT,
numberFormat
.format(totalCount == 0 ? 0
: ((inAttendanceCount + tardyCount + earlyDepartureCount) / (double) totalCount) * 100));
// set In Attendance
currentTermAttendance.put(Constants.ATTENDANCE_HISTORY_IN_ATTENDANCE, inAttendanceCount);
// set Total Absences
currentTermAttendance.put(Constants.ATTENDANCE_HISTORY_TOTAL_ABSENCES, absenceCount
+ excusedAbsenceCount + unexcusedAbsenceCount);
// set Absence
currentTermAttendance.put(Constants.ATTENDANCE_HISTORY_ABSENCE, absenceCount);
// set Excused Absences
currentTermAttendance.put(Constants.ATTENDANCE_HISTORY_EXCUSED, excusedAbsenceCount);
// set Unexcused Absences
currentTermAttendance.put(Constants.ATTENDANCE_HISTORY_UNEXCUSED, unexcusedAbsenceCount);
// set Tardy
currentTermAttendance.put(Constants.ATTENDANCE_HISTORY_TARDY, tardyCount);
// set Early departure
currentTermAttendance.put(Constants.ATTENDANCE_EARLY_DEPARTURE, earlyDepartureCount);
// Add to attendance list
attendance.appendToList("attendance", currentTermAttendance);
}
}
}
return attendance;
}
/*
* (non-Javadoc)
*
* @see
* org.slc.sli.dashboard.manager.PopulationManagerI#getSessionDates(java.lang.String,
* java.lang.String)
*/
@Override
public List<String> getSessionDates(String token, String sessionId) {
// This method appears to not return the begin/end dates for the input session, as the name
// implies, but rather the begin/end dates, respectively, for the first and last session of
// the school year associated with the input session id.
// Get the session first.
GenericEntity currentSession = entityManager.getSession(token, sessionId);
List<String> dates = new ArrayList<String>();
if (currentSession != null) {
String beginDate = currentSession.getString("beginDate");
String endDate = currentSession.getString("endDate");
List<GenericEntity> potentialSessions = entityManager.getSessionsByYear(token,
currentSession.getString("schoolYear"));
for (GenericEntity session : potentialSessions) {
if (session.getString("beginDate").compareTo(beginDate) < 0) {
beginDate = session.getString("beginDate");
}
if (session.getString("endDate").compareTo(endDate) > 0) {
endDate = session.getString("endDate");
}
}
dates.add(beginDate);
dates.add(endDate);
} else {
dates.add("");
dates.add("");
}
return dates;
}
@Override
public GenericEntity getStudentsBySearch(String token, Object nameQuery, Config.Data config) {
// Map<String, String> nameQueryMap = (Map<String, String>) nameQuery;
GenericEntity studentSearch = new GenericEntity();
// make sure the incoming nameQuery is of the proper format:
// expected is an array of Strings generated by StudentSearch.get()
String[] nameList;
try {
nameList = (String[]) nameQuery;
} catch (ClassCastException cce) {
setStudentSearchEntity(studentSearch, new LinkedList<GenericEntity>(), "", "", "", 0, 1, 50, 1, "");
return studentSearch;
}
// the query should contain at a minimum either a first or a last name
// both fields must be present in input, but one (not both) can optionally be empty
String firstName = null, lastName = null;
if (nameList.length >= 2) {
firstName = nameList[0];
lastName = nameList[1];
}
if ((firstName == null || firstName.isEmpty()) && (lastName == null || lastName.isEmpty())) {
setStudentSearchEntity(studentSearch, new LinkedList<GenericEntity>(), "", "", "", 0, 1, 50, 1, "");
return studentSearch;
}
// optionally (but typically), it should also contain pagination information
int pageNum = 1, pageSize = 50;
if (nameList.length >= 4) {
try {
pageNum = Integer.parseInt(nameList[2]);
pageSize = Integer.parseInt(nameList[3]);
} catch (NumberFormatException nfe) {
// pagination information was in an incorrect format, use default values.
pageNum = 1;
pageSize = 50;
}
}
String searchString = firstName + " " + lastName;
searchString = searchString.trim();
String searchingSchoolId = null;
if (nameList.length >= 5) {
searchingSchoolId = nameList[4];
}
if (searchingSchoolId == null || searchingSchoolId.isEmpty()) {
setStudentSearchEntity(studentSearch, new LinkedList<GenericEntity>(), searchString, firstName == null ? ""
: firstName, lastName == null ? "" : lastName, 0, pageNum, pageSize, 1, "");
return studentSearch;
}
List<GenericEntity> students = entityManager.getStudentsFromSearch(token, firstName, lastName,
searchingSchoolId);
List<GenericEntity> titleCaseStudents = entityManager.getStudentsFromSearch(token,
WordUtils.capitalize(firstName), WordUtils.capitalize(lastName), searchingSchoolId);
HashSet<GenericEntity> studentSet = new HashSet<GenericEntity>();
studentSet.addAll(students);
studentSet.addAll(titleCaseStudents);
// API returns student entities with a current attending school when searching query is used
// for school id he/she used to attend.
// Dashboard search is interested only current attending school.
// Filtering old school
Iterator<GenericEntity> studentIterator = studentSet.iterator();
while (studentIterator.hasNext()) {
GenericEntity student = studentIterator.next();
if (!searchingSchoolId.equals(student.get("schoolId"))) {
studentIterator.remove();
}
}
List<GenericEntity> enhancedStudents = new LinkedList<GenericEntity>();
HashMap<String, GenericEntity> retrievedSchools = new HashMap<String, GenericEntity>();
GenericEntity school;
Iterator<GenericEntity> studentSetIterator = studentSet.iterator();
while (studentSetIterator.hasNext()) {
GenericEntity student = studentSetIterator.next();
student = entityManager.getStudent(token, student.getId());
addFullName(student);
String schoolId = student.getString(Constants.ATTR_SCHOOL_ID);
if (schoolId != null && !schoolId.equals("")) {
if (retrievedSchools.containsKey(schoolId)) {
school = retrievedSchools.get(schoolId);
student.put("currentSchoolName", school.get(Constants.ATTR_NAME_OF_INST));
} else {
school = entityManager.getEntity(token, Constants.ATTR_SCHOOLS, schoolId, new HashMap());
retrievedSchools.put(school.getString(Constants.ATTR_ID), school);
student.put("currentSchoolName", school.get(Constants.ATTR_NAME_OF_INST));
}
}
GenericEntityEnhancer.enhanceStudent(student);
enhancedStudents.add(student);
}
// sort students by last & first name
Collections.sort(enhancedStudents, STUDENT_COMPARATOR);
// This is a temporary solution until we decide how to integrate the search with the API
// pagination calls. Currently, when API is used, the total number of search results is
// stored in the header which is not accessible. Also, code above performs two searches and
// combines results - this is a problem if API pagination is used.
int numResults = enhancedStudents.size();
// verify sensible page number was requested (negatives not allowed)
if (pageNum < 1) {
pageNum = 1;
}
// verify sensible page size was specified (negatives not allowed)
if (pageSize < 1) {
pageSize = 1;
}
// calculate the last available page from the number of results and page size
int maxPageNum = numResults / pageSize;
if (numResults % pageSize != 0) {
maxPageNum++;
}
// requested page number cannot exceed last available page
if (pageNum > maxPageNum) {
pageNum = maxPageNum;
}
// fetch the subset of search results specified by the pagination request
if (numResults > pageSize) {
int beginIndex = (pageNum - 1) * pageSize;
int endIndex = beginIndex + pageSize;
if (endIndex > numResults) {
endIndex = numResults;
}
enhancedStudents = enhancedStudents.subList(beginIndex, endIndex);
}
// fill the search map with results
setStudentSearchEntity(studentSearch, enhancedStudents, searchString, firstName, lastName, numResults, pageNum,
pageSize, maxPageNum, searchingSchoolId);
return studentSearch;
}
private void setStudentSearchEntity(GenericEntity studentSearch, List<GenericEntity> students, String searchStr,
String firstName, String lastName, int numResults, int pageNum, int pageSize, int maxPageNum,
String schoolId) {
studentSearch.put(Constants.ATTR_STUDENTS, students);
studentSearch.put(Constants.ATTR_SEARCH_STRING, searchStr);
studentSearch.put(Constants.ATTR_FIRST_NAME, firstName);
studentSearch.put(Constants.ATTR_LAST_SURNAME, lastName);
studentSearch.put(Constants.ATTR_NUM_RESULTS, numResults);
studentSearch.put(Constants.ATTR_SEARCH_PAGE_NUM, pageNum);
studentSearch.put(Constants.ATTR_SEARCH_PAGE_SIZE, pageSize);
studentSearch.put(Constants.ATTR_SEARCH_MAX_PAGE_NUM, maxPageNum);
studentSearch.put(Constants.ATTR_SCHOOL_ID, schoolId);
}
@SuppressWarnings("unchecked")
@Override
public GenericEntity getAssessments(String token, Object id, Config.Data config) {
GenericEntity entity = new GenericEntity();
GenericEntity student = entityManager.getStudentWithOptionalFields(token, (String) id,
Arrays.asList(Constants.ATTR_ASSESSMENTS));
if (student == null) {
log.error("Requested data for non-existing ID" + id);
return entity;
}
List<Map<String, Object>> assessements = filterAssessmentByFamily(
student.getList(Constants.ATTR_STUDENT_ASSESSMENTS),
(String) config.getParams().get(Constants.ATTR_ASSESSMENT_FAMILY));
// get all assessments for student
entity.put(Constants.ATTR_ASSESSMENTS, assessements);
Set<String> scoreResultNames = new LinkedHashSet<String>();
List<Map<String, Object>> scoreResults;
Map<String, Object> assessmentDetails;
List<List<Map<String, Object>>> perfLevelsDescs;
String reportingMethod;
// inline assessments, perf attributes and convert grade to gradelevel
for (Map<String, Object> elem : assessements) {
scoreResults = (List<Map<String, Object>>) elem.get(Constants.ATTR_SCORE_RESULTS);
if (scoreResults != null) {
for (Map<String, Object> oneScore : scoreResults) {
reportingMethod = (String) oneScore.get(Constants.ATTR_ASSESSMENT_REPORTING_METHOD);
scoreResultNames.add(reportingMethod);
elem.put(reportingMethod, oneScore.get(Constants.ATTR_RESULT));
}
}
assessmentDetails = (Map<String, Object>) elem.get(Constants.ATTR_ASSESSMENTS);
if (assessmentDetails != null) {
GenericEntityEnhancer.convertGradeLevel(assessmentDetails, Constants.ATTR_GRADE_LEVEL_ASSESSED);
}
perfLevelsDescs = (List<List<Map<String, Object>>>) elem.get(Constants.ATTR_PERFORMANCE_LEVEL_DESCRIPTOR);
if (perfLevelsDescs != null) {
for (List<Map<String, Object>> perfLevelsDesc : perfLevelsDescs) {
if (perfLevelsDesc != null && perfLevelsDesc.size() > 0) {
String perfLevel = (String) perfLevelsDesc.get(0).get(Constants.ATTR_CODE_VALUE);
elem.put(Constants.ATTR_PERF_LEVEL, perfLevel);
}
}
}
}
entity.put("scoreResultsSet", new ArrayList<String>(scoreResultNames));
return entity;
}
/**
* Comparator for student names
*/
public static final Comparator<GenericEntity> STUDENT_COMPARATOR = new Comparator<GenericEntity>() {
@Override
public int compare(GenericEntity o1, GenericEntity o2) {
String name1 = (String) o1.getNode(Constants.ATTR_NAME + "." + Constants.ATTR_FULL_NAME);
String name2 = (String) o2.getNode(Constants.ATTR_NAME + "." + Constants.ATTR_FULL_NAME);
if (name1 == null && name2 == null) {
return 0;
}
if (name1 == null) {
name1 = StringUtils.EMPTY;
}
return name1.compareTo(name2);
}
};
/**
* Retrieves info required to create section profile.
*/
@Override
public GenericEntity getSectionForProfile(String token, Object sectionId, Config.Data config) {
return entityManager.getSectionForProfile(token, (String) sectionId);
}
@Override
public GenericEntity getCoursesAndGrades(String token, Object studentId, Data config) {
return entityManager.getCurrentCoursesAndGrades(token, (String) studentId);
}
/**
* Retrieves attendance in a sorted order, removes all events where the student is present.
* Returns a GenericEntity with startDate, endDate, and attendanceList.
*/
@Override
public GenericEntity getStudentAttendanceForCalendar(String token, Object studentId, Data config) {
GenericEntity ge = new GenericEntity();
List<GenericEntity> attendanceList = getStudentAttendance(token, (String) studentId, null, null);
if (attendanceList == null || attendanceList.size() < 1) {
return ge;
}
GenericEntity firstWrapper = attendanceList.get(0);
String schoolId = (String) firstWrapper.get("schoolId");
List<Map<String, Object>> schoolYearAttendance = (List<Map<String, Object>>) firstWrapper
.get(Constants.ATTR_ATTENDANCE_SCHOOLYEAR_ATTENDANCE);
if (schoolYearAttendance == null || schoolYearAttendance.size() < 1) {
return ge;
}
// Comparator, sort by "schoolYear" descending order
Comparator<Map<String, Object>> schoolYearAttendanceComparator = new Comparator<Map<String, Object>>() {
@Override
public int compare(Map<String, Object> arg0, Map<String, Object> arg1) {
Object schoolYearObj0 = arg0.get(Constants.ATTR_SCHOOL_YEAR);
Object schoolYearObj1 = arg1.get(Constants.ATTR_SCHOOL_YEAR);
if (schoolYearObj0 == null || schoolYearObj1 == null) {
return 0;
}
String schoolYear0 = schoolYearObj0.toString();
String schoolYear1 = schoolYearObj1.toString();
return schoolYear1.compareTo(schoolYear0);
}
};
Collections.sort(schoolYearAttendance, schoolYearAttendanceComparator);
Map<String, Object> secondWrapper = schoolYearAttendance.get(0);
List<Map> attList = (List<Map>) secondWrapper.get(Constants.ATTR_ATTENDANCE_ATTENDANCE_EVENT);
if (attList == null) {
return ge;
}
LinkedList<Map> absentList = new LinkedList<Map>();
List<String> currentYearDates = null;
try {
// get begin/end dates for the current school year
currentYearDates = getCurrentYearDates(token, schoolId);
// filter out 'In Attendance' events, remove whitespace
for (Map attEvent : attList) {
String event = (String) attEvent.get(Constants.ATTR_ATTENDANCE_EVENT_CATEGORY);
if (!event.equals(Constants.ATTR_ATTENDANCE_IN_ATTENDANCE)) {
String strippedWhiteSpaceEvent = ((String) attEvent.get(Constants.ATTR_ATTENDANCE_EVENT_CATEGORY))
.replace(" ", "");
attEvent.put(Constants.ATTR_ATTENDANCE_EVENT_CATEGORY, strippedWhiteSpaceEvent);
absentList.addLast(attEvent);
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
ge.put(Constants.ATTR_ATTENDANCE_LIST, absentList);
ge.put(Constants.ATTR_START_DATE, currentYearDates.get(0));
ge.put(Constants.ATTR_END_DATE, currentYearDates.get(1));
return ge;
}
/**
* Returns the begin and end dates of the current school year.
* The current school year is determined by comparing the current date to
* the begin/end dates for sessions the user can see. If the current date
* is not in any session, it returns the most recent session.
*
* @param token
* @return
* @throws Exception
*/
private List<String> getCurrentYearDates(String token, String schoolId) throws Exception {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
List<GenericEntity> sessions = getApiClient().getSessions(token, schoolId, null);
// sort sessions latest to earliest
Comparator<Map> c = new Comparator<Map>() {
@Override
public int compare(Map arg0, Map arg1) {
Map<String, String> map1, map2;
map1 = arg0;
map2 = arg1;
Date date1, date2;
try {
date1 = sdf.parse(map1.get(Constants.ATTR_BEGIN_DATE));
date2 = sdf.parse(map2.get(Constants.ATTR_BEGIN_DATE));
return date2.compareTo(date1);
} catch (ParseException e) {
return 0;
}
}
};
Collections.sort(sessions, c);
// find the current year
String schoolYear = null;
for (GenericEntity session : sessions) {
Date currentDate = new Date();
Date sessionStart = sdf.parse(session.getString(Constants.ATTR_BEGIN_DATE));
Date sessionEnd = sdf.parse(session.getString(Constants.ATTR_END_DATE));
if (sessionStart != null && sessionStart.before(currentDate) && sessionEnd != null
&& sessionEnd.after(currentDate)) {
schoolYear = session.getString(Constants.ATTR_SCHOOL_YEAR);
break;
} else if (sessionEnd.before(currentDate)) {
schoolYear = session.getString(Constants.ATTR_SCHOOL_YEAR);
break;
}
}
Date startDate = null;
Date endDate = null;
// find all sessions for current school year, find earliest start date, latest end date
for (GenericEntity session : sessions) {
if (session.getString(Constants.ATTR_SCHOOL_YEAR).equals(schoolYear)) {
Date sessionStart = sdf.parse(session.getString(Constants.ATTR_BEGIN_DATE));
Date sessionEnd = sdf.parse(session.getString(Constants.ATTR_END_DATE));
if (startDate == null || sessionStart.before(startDate)) {
startDate = sessionStart;
}
if (endDate == null || sessionEnd.after(endDate)) {
endDate = sessionEnd;
}
}
}
List<String> ret = new ArrayList<String>();
ret.add(sdf.format(startDate));
ret.add(sdf.format(endDate));
return ret;
}
@Override
public GenericEntity getEdorgProfile(String token, Object edorgId, Config.Data config) {
return entityManager.getEdorgProfile(token, (String) edorgId);
}
@Override
public GenericEntity getStateEdorgProfile(String token, Object edorgId, Config.Data config) {
return entityManager.getStateEdorgProfile(token, (String) edorgId);
}
}