/**********************************************************************************
*
* $Id: CourseGradeDetailsBean.java 111275 2012-08-13 18:14:06Z savithap@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.opensource.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.Arrays;
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.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.model.SelectItem;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.component.cover.ServerConfigurationService;
import org.sakaiproject.jsf.spreadsheet.SpreadsheetDataFileWriterCsv;
import org.sakaiproject.jsf.spreadsheet.SpreadsheetDataFileWriterXls;
import org.sakaiproject.jsf.spreadsheet.SpreadsheetDataFileWriterPdf;
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 String standardExportDefaultFields;
private boolean allStudentsViewOnly = true;
private boolean enableCustomExport;
//Export Field data options
private boolean includeUsereid;
private boolean includeSortname;
private boolean includeCoursegrade;
private boolean includeFinalscore;
private boolean includeCalculatedgrade;
private boolean includeGradeoverride;
private boolean includeLastmodifieddate;
private String exportType;
private List<SelectItem> exportFormats = new ArrayList<SelectItem>();
public String getExportType() {
return exportType;
}
public void setExportType(String exportType) {
this.exportType = exportType;
}
public List<SelectItem> getExportFormats() {
return exportFormats;
}
public void setExportFormats(List<SelectItem> exportFormats) {
this.exportFormats = exportFormats;
}
public boolean isIncludeUsereid() {
return includeUsereid;
}
public void setIncludeUsereid(boolean includeUsereid) {
this.includeUsereid = includeUsereid;
}
public boolean isIncludeSortname() {
return includeSortname;
}
public void setIncludeSortname(boolean includeSortname) {
this.includeSortname = includeSortname;
}
public boolean isIncludeCoursegrade() {
return includeCoursegrade;
}
public void setIncludeCoursegrade(boolean includeCoursegrade) {
this.includeCoursegrade = includeCoursegrade;
}
public boolean isIncludeFinalscore() {
return includeFinalscore;
}
public void setIncludeFinalscore(boolean includeFinalscore) {
this.includeFinalscore = includeFinalscore;
}
public boolean isIncludeCalculatedgrade() {
return includeCalculatedgrade;
}
public void setIncludeCalculatedgrade(boolean includeCalculatedgrade) {
this.includeCalculatedgrade = includeCalculatedgrade;
}
public boolean isIncludeGradeoverride() {
return includeGradeoverride;
}
public void setIncludeGradeoverride(boolean includeGradeoverride) {
this.includeGradeoverride = includeGradeoverride;
}
public boolean isIncludeLastmodifieddate() {
return includeLastmodifieddate;
}
public void setIncludeLastmodifieddate(boolean includeLastmodifieddate) {
this.includeLastmodifieddate = includeLastmodifieddate;
}
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());
enableCustomExport = ServerConfigurationService.getBoolean("gradebook.institutional.export.enabled",false);
//Default standard export fields
standardExportDefaultFields = ServerConfigurationService.getString("gradebook.standard.export.default.fields","usereid,sortname,coursegrade");
updateExportFieldStatus(standardExportDefaultFields);
if (exportFormats.isEmpty()){
exportFormats.add(new SelectItem("CSV"));
exportFormats.add(new SelectItem("Excel"));
exportFormats.add(new SelectItem("PDF"));
}
exportType = "CSV";
// 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;
}
/**
* @return the value of gradebook.institutional.export.enabled sakai-property
*/
public boolean isEnableCustomExport() {
return enableCustomExport;
}
/**
* 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"));
}
public void updateExportFieldStatus(String standardExportFields){
if(standardExportFields != null && standardExportFields.length()>1){
standardExportFields = standardExportFields.toLowerCase();
if(standardExportFields.contains("usereid")){
setIncludeUsereid(true);
}
if(standardExportFields.contains("sortname")){
setIncludeSortname(true);
}
if(standardExportFields.contains("coursegrade")){
setIncludeCoursegrade(true);
}
if(standardExportFields.contains("calculatedgrade")){
setIncludeCalculatedgrade(true);
}
if(standardExportFields.contains("gradeoverride")){
setIncludeGradeoverride(true);
}
if(standardExportFields.contains("finalscore")){
setIncludeFinalscore(true);
}
if(standardExportFields.contains("lastmodifieddate")){
setIncludeLastmodifieddate(true);
}
}
}
//Action to export
public void export(ActionEvent event){
//FacesContext facesContext = FacesContext.getCurrentInstance();
//HttpServletRequest request = (HttpServletRequest) facesContext.getExternalContext().getRequest();
List<String> fields = new ArrayList<String>();
if(includeSortname) fields.add("sortname");
if(includeUsereid) fields.add("usereid");
if(includeFinalscore) fields.add("finalscore");
if(includeCalculatedgrade) fields.add("calculatedgrade");
if(includeLastmodifieddate) fields.add("lastmodifieddate");
if(includeGradeoverride) fields.add("gradeoverride");
if(includeCoursegrade) fields.add("coursegrade");
getGradebookBean().getEventTrackingService().postEvent("gradebook.downloadCourseGrade","/gradebook/"+getGradebookId()+"/"+getAuthzLevel());
if(exportType.equalsIgnoreCase("CSV")){
if(logger.isInfoEnabled()) logger.info("exporting course grade as CSV for gradebook " + getGradebookUid());
SpreadsheetUtil.downloadSpreadsheetData(getSpreadsheetData("csv", fields),
getDownloadFileName(getLocalizedString("export_course_grade_prefix")),
new SpreadsheetDataFileWriterCsv());
}
else if(exportType.equalsIgnoreCase("Excel")){
if(logger.isInfoEnabled()) logger.info("exporting course grade as Excel for gradebook " + getGradebookUid());
SpreadsheetUtil.downloadSpreadsheetData(getSpreadsheetData("excel", fields),
getDownloadFileName(getLocalizedString("export_course_grade_prefix")),
new SpreadsheetDataFileWriterXls());
}
else if(exportType.equalsIgnoreCase("PDF")){
if(logger.isInfoEnabled()) logger.info("exporting course grade as PDF for gradebook " + getGradebookUid());
SpreadsheetUtil.downloadSpreadsheetData(getSpreadsheetData("pdf", fields),
getDownloadFileName(getLocalizedString("export_course_grade_prefix")),
new SpreadsheetDataFileWriterPdf());
}
}
//Action to export institutional customized format of gradebook SAK-22204
public void exportCustomCsv(ActionEvent event){
if(logger.isInfoEnabled()) logger.info("exporting course grade as Institutional CSV for gradebook " + getGradebookUid());
getGradebookBean().getEventTrackingService().postEvent("gradebook.downloadCourseGrade","/gradebook/"+getGradebookId()+"/"+getAuthzLevel());
String defaultFields = "userEid,sortName,courseGrade";
String stringFields = ServerConfigurationService.getString("gradebook.institutional.export.fields",defaultFields);
String[] fields = stringFields.replaceAll("\\s","").toLowerCase().split(",");
if (fields.length == 0) fields = defaultFields.split(",");
SpreadsheetUtil.downloadSpreadsheetData(getSpreadsheetData("customCsv", Arrays.asList(fields)),
getDownloadFileName(getLocalizedString("export_course_grade_prefix")),
new SpreadsheetDataFileWriterCsv());
}
//Export custom grade label
public String getExportCustomLabel(){
return ServerConfigurationService.getString("gradebook.institutional.export.label",getLocalizedString("course_grade_details_export_course_grades_institution"));
}
private List<List<Object>> getSpreadsheetData(String type, List<String> fields) {
// 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 = null;
//For institutional customized export format SAK-22204
if (type.startsWith("custom")){
converter = new CourseGradesToSpreadsheetCustomConverter();
} else {
converter = (CourseGradesToSpreadsheetConverter)getGradebookBean().getConfigurationBean().getPlugin(courseGradesConverterPlugin);
}
return converter.getSpreadsheetData(filteredEnrollments, courseGrade, filteredGradesMap, fields);
}
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;
}
}