/********************************************************************************** * * $Id: CourseGradeDetailsBean.java 59674 2009-04-03 23:05:58Z arwhyte@umich.edu $ * *********************************************************************************** * * Copyright (c) 2005, 2006, 2007, 2008 The Sakai Foundation, The MIT Corporation * * Licensed under the Educational Community 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.osedu.org/licenses/ECL-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.sakaiproject.tool.gradebook.ui; import java.math.BigDecimal; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; 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 javax.faces.event.ActionEvent; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.jsf.spreadsheet.SpreadsheetDataFileWriterCsv; import org.sakaiproject.jsf.spreadsheet.SpreadsheetDataFileWriterXls; import org.sakaiproject.jsf.spreadsheet.SpreadsheetUtil; import org.sakaiproject.section.api.coursemanagement.EnrollmentRecord; import org.sakaiproject.service.gradebook.shared.GradebookService; import org.sakaiproject.service.gradebook.shared.StaleObjectModificationException; import org.sakaiproject.tool.gradebook.Assignment; import org.sakaiproject.tool.gradebook.CourseGrade; import org.sakaiproject.tool.gradebook.CourseGradeRecord; import org.sakaiproject.tool.gradebook.CourseGradesToSpreadsheetConverter; import org.sakaiproject.tool.gradebook.GradeMapping; import org.sakaiproject.tool.gradebook.GradingEvent; import org.sakaiproject.tool.gradebook.GradingEvents; import org.sakaiproject.tool.gradebook.jsf.FacesUtil; public class CourseGradeDetailsBean extends EnrollmentTableBean { private static final Log logger = LogFactory.getLog(CourseGradeDetailsBean.class); // View maintenance fields - serializable. private List scoreRows; private CourseGrade courseGrade; private List updatedGradeRecords; private GradeMapping gradeMapping; private double totalPoints; private String courseGradesConverterPlugin; private boolean allStudentsViewOnly = true; public class ScoreRow implements Serializable { private EnrollmentRecord enrollment; private CourseGradeRecord courseGradeRecord; private List eventRows; private boolean userCanGrade; public ScoreRow() { } public ScoreRow(EnrollmentRecord enrollment, CourseGradeRecord courseGradeRecord, List gradingEvents, boolean userCanGrade) { this.enrollment = enrollment; this.courseGradeRecord = courseGradeRecord; this.userCanGrade = userCanGrade; eventRows = new ArrayList(); for (Iterator iter = gradingEvents.iterator(); iter.hasNext();) { GradingEvent gradingEvent = (GradingEvent)iter.next(); eventRows.add(new GradingEventRow(gradingEvent)); } } /** * * @return letter grade representation of grade or null if no course grade yet */ public String getCalculatedLetterGrade() { Double grade = courseGradeRecord.getAutoCalculatedGrade(); String letterGrade = null; if (grade != null) letterGrade = gradeMapping.getGrade(courseGradeRecord.getNonNullAutoCalculatedGrade()); return letterGrade; } /** * Because the PrecisePercentageConverter is actually rounding at 2 decimals instead of * truncating, do the formatting here. * @return percent representation of grade or null if no grade yet */ public Double getCalculatedPercentGrade() { Double grade = courseGradeRecord.getAutoCalculatedGrade(); if (grade != null) { // to emulate the converter, truncate to 4 decimal places, then return 2 grade = new Double(FacesUtil.getRoundDown(grade.doubleValue(), 4)); BigDecimal bdGrade = (new BigDecimal(grade.toString())).setScale(2, BigDecimal.ROUND_DOWN); grade = new Double(bdGrade.doubleValue()); } return grade; } public CourseGradeRecord getCourseGradeRecord() { return courseGradeRecord; } public void setCourseGradeRecord(CourseGradeRecord courseGradeRecord) { this.courseGradeRecord = courseGradeRecord; } public String getEnteredGrade() { return courseGradeRecord.getEnteredGrade(); } public void setEnteredGrade(String enteredGrade) { String originalEnteredGrade = courseGradeRecord.getEnteredGrade(); if (!StringUtils.equals(enteredGrade, originalEnteredGrade)) { courseGradeRecord.setEnteredGrade(enteredGrade); updatedGradeRecords.add(courseGradeRecord); } } public EnrollmentRecord getEnrollment() { return enrollment; } public List getEventRows() { return eventRows; } public String getEventsLogTitle() { return FacesUtil.getLocalizedString("course_grade_details_log_title", new String[] {enrollment.getUser().getDisplayName()}); } public boolean isUserCanGrade() { return userCanGrade; } } protected void init() { super.init(); // Clear view state. scoreRows = new ArrayList(); courseGrade = getGradebookManager().getCourseGrade(getGradebookId()); updatedGradeRecords = new ArrayList(); gradeMapping = getGradebook().getSelectedGradeMapping(); totalPoints = getGradebookManager().getTotalPoints(getGradebookId()); // Set up score rows. Map enrollmentMap = getOrderedEnrollmentMapForCourseGrades(); List studentUids = new ArrayList(enrollmentMap.keySet()); List gradeRecords = getGradebookManager().getPointsEarnedCourseGradeRecordsWithStats(courseGrade, studentUids); if (!isEnrollmentSort()) { // Need to sort and page based on a scores column. String sortColumn = getSortColumn(); Comparator comparator = null; if (sortColumn.equals(CourseGrade.SORT_BY_CALCULATED_GRADE) || sortColumn.equals(CourseGrade.SORT_BY_POINTS_EARNED)) { comparator = CourseGradeRecord.calcComparator; } else if (sortColumn.equals(CourseGrade.SORT_BY_OVERRIDE_GRADE)) { comparator = CourseGradeRecord.getOverrideComparator(courseGrade.getGradebook().getSelectedGradeMapping()); } if (comparator != null) { Collections.sort(gradeRecords, comparator); } List scoreSortedStudentUids = new ArrayList(); for(Iterator iter = gradeRecords.iterator(); iter.hasNext();) { CourseGradeRecord cgr = (CourseGradeRecord)iter.next(); scoreSortedStudentUids.add(cgr.getStudentId()); } // Put enrollments with no scores at the beginning of the final list. studentUids.removeAll(scoreSortedStudentUids); // Add all sorted enrollments with scores into the final list studentUids.addAll(scoreSortedStudentUids); studentUids = finalizeSortingAndPaging(studentUids); } // Get all of the grading events for these enrollments on this assignment GradingEvents allEvents = getGradebookManager().getGradingEvents(courseGrade, studentUids); Map gradeRecordMap = new HashMap(); for (Iterator iter = gradeRecords.iterator(); iter.hasNext(); ) { CourseGradeRecord gradeRecord = (CourseGradeRecord)iter.next(); if (studentUids.contains(gradeRecord.getStudentId())) { gradeRecordMap.put(gradeRecord.getStudentId(), gradeRecord); } } // If the table is not being sorted by enrollment information, then // we had to gather grade records for all students to set up the // current page. In that case, eliminate the undisplayed grade records // to reduce data contention. if (!isEnrollmentSort()) { gradeRecords = new ArrayList(gradeRecordMap.values()); } for (Iterator iter = studentUids.iterator(); iter.hasNext(); ) { String studentUid = (String)iter.next(); Map enrFunctionMap = (Map) enrollmentMap.get(studentUid); List enrRecList = new ArrayList(enrFunctionMap.keySet()); EnrollmentRecord enrollment = (EnrollmentRecord)enrRecList.get(0); // there is only one rec in this map CourseGradeRecord gradeRecord = (CourseGradeRecord)gradeRecordMap.get(studentUid); if(gradeRecord == null) { gradeRecord = new CourseGradeRecord(courseGrade, studentUid); gradeRecords.add(gradeRecord); } boolean userCanGrade = false; String itemFunction = (String)enrFunctionMap.get(enrollment); if (itemFunction != null && itemFunction.equalsIgnoreCase(GradebookService.gradePermission)) { userCanGrade = true; allStudentsViewOnly = false; } scoreRows.add(new ScoreRow(enrollment, gradeRecord, allEvents.getEvents(studentUid), userCanGrade)); } } public CourseGrade getCourseGrade() { return courseGrade; } public String getAverageCourseGrade() { return gradeMapping.getGrade(courseGrade.getMean()); } public double getTotalPoints() { return totalPoints; } public boolean isAllStudentsViewOnly() { return allStudentsViewOnly; } /** * Action listener to update grades. * NOTE: No transient fields are available yet. */ public void processUpdateGrades(ActionEvent event) { try { saveGrades(); } catch (StaleObjectModificationException e) { logger.error(e); FacesUtil.addErrorMessage(getLocalizedString("course_grade_details_locking_failure")); } } /** * Action to calculate course grades */ public String processCalculateCourseGrades() { try { calculateCourseGrades(); } catch (StaleObjectModificationException e) { logger.error(e); FacesUtil.addErrorMessage(getLocalizedString("course_grade_details_locking_failure")); } return "courseGradeDetails"; } private void saveGrades() throws StaleObjectModificationException { getGradebookManager().updateCourseGradeRecords(courseGrade, updatedGradeRecords); getGradebookBean().getEventTrackingService().postEvent("gradebook.updateCourseGrades","/gradebook/"+getGradebookId()+"/"+updatedGradeRecords.size()+"/"+getAuthzLevel()); // Let the user know. FacesUtil.addMessage(getLocalizedString("course_grade_details_grades_saved")); } private void calculateCourseGrades() { getGradebookManager().fillInZeroForNullGradeRecords(getGradebook()); FacesUtil.addMessage(getLocalizedString("calculate_course_grade_done")); } // Download spreadsheet of course grades. It's very likely that insitutions will // want to customize this somewhere along the way. public void exportCsv(ActionEvent event){ if(logger.isInfoEnabled()) logger.info("exporting course grade as CSV for gradebook " + getGradebookUid()); getGradebookBean().getEventTrackingService().postEvent("gradebook.downloadCourseGrade","/gradebook/"+getGradebookId()+"/"+getAuthzLevel()); SpreadsheetUtil.downloadSpreadsheetData(getSpreadsheetData(), getDownloadFileName(getLocalizedString("export_course_grade_prefix")), new SpreadsheetDataFileWriterCsv()); } public void exportExcel(ActionEvent event){ if(logger.isInfoEnabled()) logger.info("exporting course grade as Excel for gradebook " + getGradebookUid()); getGradebookBean().getEventTrackingService().postEvent("gradebook.downloadCourseGrade","/gradebook/"+getGradebookId()+"/"+getAuthzLevel()); SpreadsheetUtil.downloadSpreadsheetData(getSpreadsheetData(), getDownloadFileName(getLocalizedString("export_course_grade_prefix")), new SpreadsheetDataFileWriterXls()); } private List<List<Object>> getSpreadsheetData() { // Get the full list of filtered enrollments and scores (not just the current page's worth). List<EnrollmentRecord> filteredEnrollments = new ArrayList(getWorkingEnrollmentsForCourseGrade().keySet()); Collections.sort(filteredEnrollments, ENROLLMENT_NAME_COMPARATOR); Set<String> studentUids = new HashSet<String>(); for (EnrollmentRecord enrollment : filteredEnrollments) { studentUids.add(enrollment.getUser().getUserUid()); } CourseGrade courseGrade = getGradebookManager().getCourseGrade(getGradebookId()); List<CourseGradeRecord> courseGradeRecords = getGradebookManager().getPointsEarnedCourseGradeRecords(courseGrade, studentUids); Map<String, CourseGradeRecord> filteredGradesMap = new HashMap<String, CourseGradeRecord>(); getGradebookManager().addToGradeRecordMap(filteredGradesMap, courseGradeRecords); CourseGradesToSpreadsheetConverter converter = (CourseGradesToSpreadsheetConverter)getGradebookBean().getConfigurationBean().getPlugin(courseGradesConverterPlugin); return converter.getSpreadsheetData(filteredEnrollments, courseGrade, filteredGradesMap); } public List getScoreRows() { return scoreRows; } public void setScoreRows(List scoreRows) { this.scoreRows = scoreRows; } public String getEventsLogType() { return FacesUtil.getLocalizedString("course_grade_details_log_type"); } // Sorting public boolean isSortAscending() { return getPreferencesBean().isCourseGradeDetailsTableSortAscending(); } public void setSortAscending(boolean sortAscending) { getPreferencesBean().setCourseGradeDetailsTableSortAscending(sortAscending); } public String getSortColumn() { return getPreferencesBean().getCourseGradeDetailsTableSortColumn(); } public void setSortColumn(String sortColumn) { getPreferencesBean().setCourseGradeDetailsTableSortColumn(sortColumn); } public void setCourseGradesConverterPlugin(String courseGradesConverterPlugin) { this.courseGradesConverterPlugin = courseGradesConverterPlugin; } }