/*******************************************************************************
* Copyright (c) 2006, 2007, 2008, 2009 The Sakai Foundation
*
* 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.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
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.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.section.api.coursemanagement.EnrollmentRecord;
import org.sakaiproject.service.gradebook.shared.StaleObjectModificationException;
import org.sakaiproject.tool.cover.SessionManager;
import org.sakaiproject.tool.gradebook.AbstractGradeRecord;
import org.sakaiproject.tool.gradebook.Assignment;
import org.sakaiproject.tool.gradebook.AssignmentGradeRecord;
import org.sakaiproject.tool.gradebook.Category;
import org.sakaiproject.tool.gradebook.CourseGrade;
import org.sakaiproject.tool.gradebook.CourseGradeRecord;
import org.sakaiproject.tool.gradebook.GradableObject;
import org.sakaiproject.tool.gradebook.jsf.FacesUtil;
/**
* Provides data for the instructor's view of a student's grades in the gradebook.
*/
public class InstructorViewBean extends ViewByStudentBean implements Serializable {
private static Log logger = LogFactory.getLog(InstructorViewBean.class);
private EnrollmentRecord previousStudent;
private EnrollmentRecord nextStudent;
private EnrollmentRecord currentStudent;
private String studentEmailAddress;
private String studentSections;
private List orderedEnrollmentList;
// parameters passed to the page
private String returnToPage;
private String assignmentId; // for returning to specific gradebook item
private static final String ROSTER_PAGE = "roster";
private static final String ASSIGN_DETAILS_PAGE = "assignmentDetails";
/**
* @see org.sakaiproject.tool.gradebook.ui.InitializableBean#init()
*/
public void init() {
previousStudent = null;
nextStudent = null;
studentSections = null;
String sortByCol = getSortColumn();
boolean sortAsc = isSortAscending();
setIsInstructorView(true);
if (getStudentUid() != null) {
super.init();
studentEmailAddress = getUserDirectoryService().getUserEmailAddress(getStudentUid());
studentSections = getStudentSectionsForDisplay();
// set up the "next" and "previous" student navigation
// TODO preserve filter/sort status from originating page
if (orderedEnrollmentList == null) {
orderedEnrollmentList = new ArrayList();
if (returnToPage.equals(ROSTER_PAGE)) {
super.setSelectedSectionFilterValue(getPreferencesBean().getRosterTableSectionFilter());
maxDisplayedScoreRows = 0;
orderedEnrollmentList = getOrderedEnrolleesFromRosterPage();
} else if (returnToPage.equals(ASSIGN_DETAILS_PAGE)) {
super.setSelectedSectionFilterValue(getPreferencesBean().getAssignmentDetailsTableSectionFilter());
maxDisplayedScoreRows = 0;
orderedEnrollmentList = getOrderedEnrolleesFromAssignDetailsPage();
}
}
if (orderedEnrollmentList != null && orderedEnrollmentList.size() > 1) {
int index = 0;
while (index < orderedEnrollmentList.size()) {
EnrollmentRecord enrollee = (EnrollmentRecord)orderedEnrollmentList.get(index);
if (enrollee.getUser().getUserUid().equals(getStudentUid())) {
currentStudent = enrollee;
if (index-1 >= 0) {
previousStudent = (EnrollmentRecord) orderedEnrollmentList.get(index-1);
}
if (index+1 < orderedEnrollmentList.size()) {
nextStudent = (EnrollmentRecord) orderedEnrollmentList.get(index+1);
}
break;
}
index++;
}
}
setSortColumn(sortByCol);
setSortAscending(sortAsc);
}
}
// Navigation
public EnrollmentRecord getPreviousStudent() {
return previousStudent;
}
public EnrollmentRecord getNextStudent() {
return nextStudent;
}
public boolean isFirst() {
return previousStudent == null;
}
public boolean isLast() {
return nextStudent == null;
}
public EnrollmentRecord getCurrentStudent() {
return currentStudent;
}
/**
* @return text for "Return to" button
*/
public String getReturnToPageButtonName() {
String pageTitle;
if (ASSIGN_DETAILS_PAGE.equals(returnToPage))
pageTitle = getLocalizedString("assignment_details_page_title");
else
pageTitle = getLocalizedString("roster_page_title");
return getLocalizedString("inst_view_return_to", new String[] {pageTitle});
}
/**
*
* @return page title of originating page
*/
public String getReturnToPageName() {
if (returnToPage.equals(ASSIGN_DETAILS_PAGE))
return getLocalizedString("assignment_details_page_title");
else
return getLocalizedString("roster_page_title");
}
/**
*
* @return comma-separated string of user's section/group memberships
*/
public String getStudentSections() {
return studentSections;
}
/**
* @return studentEmailAddress
*/
public String getStudentEmailAddress() {
return studentEmailAddress;
}
/**
* Action listener to view a different student
*/
public void processStudentUidChange(ActionEvent event) {
Map params = FacesUtil.getEventParameterMap(event);
if (logger.isDebugEnabled())
logger.debug("processStudentUidChange params=" + params + ", current studentUid=" + getStudentUid());
// run the updates before changing the student id
processUpdateScoresForPreNextStudent();
String idParam = (String)params.get("studentUid");
if (idParam != null) {
setStudentUid(idParam);
}
}
public void processUpdateScoresForPreNextStudent() {
try {
saveScoresWithoutConfirmation();
} catch (StaleObjectModificationException e) {
FacesUtil.addErrorMessage(getLocalizedString("assignment_details_locking_failure"));
}
}
/**
* Save the input scores for the user
* @throws StaleObjectModificationException
*/
public void saveScoresWithoutConfirmation() throws StaleObjectModificationException {
if (logger.isInfoEnabled()) logger.info("saveScores for " + getStudentUid());
// first, determine which scores were updated
List updatedGradeRecords = new ArrayList();
if (getGradebookItems() != null) {
Iterator itemIter = getGradebookItems().iterator();
while (itemIter.hasNext()) {
Object item = itemIter.next();
if (item instanceof AssignmentGradeRow) {
AssignmentGradeRow gradeRow = (AssignmentGradeRow) item;
AssignmentGradeRecord gradeRecord = gradeRow.getGradeRecord();
if (gradeRecord == null && (gradeRow.getScore() != null || gradeRow.getLetterScore() != null)) {
// this is a new grade
gradeRecord = new AssignmentGradeRecord(gradeRow.getAssociatedAssignment(), getStudentUid(), null);
}
if (gradeRecord != null) {
if (getGradeEntryByPoints()) {
Double originalScore = null;
originalScore = gradeRecord.getPointsEarned();
if (originalScore != null) {
// truncate to two decimals for more accurate comparison
originalScore = new Double(FacesUtil.getRoundDown(originalScore.doubleValue(), 2));
}
Double newScore = gradeRow.getScore();
if ( (originalScore != null && !originalScore.equals(newScore)) ||
(originalScore == null && newScore != null) ) {
gradeRecord.setPointsEarned(newScore);
updatedGradeRecords.add(gradeRecord);
}
} else if(getGradeEntryByPercent()) {
Double originalScore = null;
originalScore = gradeRecord.getPercentEarned();
if (originalScore != null) {
// truncate to two decimals for more accurate comparison
originalScore = new Double(FacesUtil.getRoundDown(originalScore.doubleValue(), 2));
}
Double newScore = gradeRow.getScore();
if ( (originalScore != null && !originalScore.equals(newScore)) ||
(originalScore == null && newScore != null) ) {
gradeRecord.setPercentEarned(newScore);
updatedGradeRecords.add(gradeRecord);
}
} else if (getGradeEntryByLetter()) {
String originalScore = gradeRecord.getLetterEarned();
String newScore = gradeRow.getLetterScore();
if ( (originalScore != null && !originalScore.equals(newScore)) ||
(originalScore == null && newScore != null) ) {
gradeRecord.setLetterEarned(newScore);
updatedGradeRecords.add(gradeRecord);
}
}
}
}
}
}
Set excessiveScores = getGradebookManager().updateStudentGradeRecords(updatedGradeRecords, getGradebook().getGrade_type(), getStudentUid());
if(updatedGradeRecords.size() > 0){
getGradebookBean().getEventTrackingService().postEvent("gradebook.updateItemScores","/gradebook/"+getGradebookId()+"/"+updatedGradeRecords.size()+"/"+getAuthzLevel());
}
}
/**
* Action listener to update scores.
*/
public void processUpdateScores() {
try {
saveScores();
} catch (StaleObjectModificationException e) {
FacesUtil.addErrorMessage(getLocalizedString("assignment_details_locking_failure"));
}
}
/**
* Save the input scores for the user
* @throws StaleObjectModificationException
*/
public void saveScores() throws StaleObjectModificationException {
if (logger.isInfoEnabled()) logger.info("saveScores for " + getStudentUid());
// first, determine which scores were updated
List updatedGradeRecords = new ArrayList();
if (getGradebookItems() != null) {
Iterator itemIter = getGradebookItems().iterator();
while (itemIter.hasNext()) {
Object item = itemIter.next();
if (item instanceof AssignmentGradeRow) {
AssignmentGradeRow gradeRow = (AssignmentGradeRow) item;
AssignmentGradeRecord gradeRecord = gradeRow.getGradeRecord();
if (gradeRecord == null && (gradeRow.getScore() != null || gradeRow.getLetterScore() != null)) {
// this is a new grade
gradeRecord = new AssignmentGradeRecord(gradeRow.getAssociatedAssignment(), getStudentUid(), null);
}
if (gradeRecord != null) {
if (getGradeEntryByPoints()) {
Double originalScore = null;
originalScore = gradeRecord.getPointsEarned();
if (originalScore != null) {
// truncate to two decimals for more accurate comparison
originalScore = new Double(FacesUtil.getRoundDown(originalScore.doubleValue(), 2));
}
Double newScore = gradeRow.getScore();
if ( (originalScore != null && !originalScore.equals(newScore)) ||
(originalScore == null && newScore != null) ) {
gradeRecord.setPointsEarned(newScore);
updatedGradeRecords.add(gradeRecord);
getGradebookBean().getEventTrackingService().postEvent("gradebook.updateItemScore","/gradebook/"+getGradebookUid()+"/"+gradeRecord.getAssignment().getName()+"/"+gradeRecord.getStudentId()+"/"+gradeRecord.getPointsEarned()+"/"+getAuthzLevel());
}
} else if(getGradeEntryByPercent()) {
Double originalScore = null;
originalScore = gradeRecord.getPercentEarned();
if (originalScore != null) {
// truncate to two decimals for more accurate comparison
originalScore = new Double(FacesUtil.getRoundDown(originalScore.doubleValue(), 2));
}
Double newScore = gradeRow.getScore();
if ( (originalScore != null && !originalScore.equals(newScore)) ||
(originalScore == null && newScore != null) ) {
gradeRecord.setPercentEarned(newScore);
updatedGradeRecords.add(gradeRecord);
}
} else if (getGradeEntryByLetter()) {
String originalScore = gradeRecord.getLetterEarned();
String newScore = gradeRow.getLetterScore();
if ( (originalScore != null && !originalScore.equals(newScore)) ||
(originalScore == null && newScore != null) ) {
gradeRecord.setLetterEarned(newScore);
updatedGradeRecords.add(gradeRecord);
}
}
}
}
}
}
Set excessiveScores = getGradebookManager().updateStudentGradeRecords(updatedGradeRecords, getGradebook().getGrade_type(), getStudentUid());
if(updatedGradeRecords.size() > 0){
getGradebookBean().getEventTrackingService().postEvent("gradebook.updateItemScores","/gradebook/"+getGradebookId()+"/"+updatedGradeRecords.size()+"/"+getAuthzLevel());
String messageKey = (excessiveScores.size() > 0) ?
"inst_view_scores_saved_excessive" :
"inst_view_scores_saved";
// Let the user know.
FacesUtil.addMessage(getLocalizedString(messageKey));
}
}
private String getColumnHeader(GradableObject gradableObject) {
if (gradableObject.isCourseGrade()) {
return getLocalizedString("roster_course_grade_column_name");
} else {
return ((Assignment)gradableObject).getName();
}
}
/**
* If we came to the instructor view from the roster page, we need to
* set the previous and next student info according to the order and filter
* on the roster page
* @return
*/
private List getOrderedEnrolleesFromRosterPage() {
// it may be sorted by name, id, cumulative score, or any of the individual
// assignments
setSortColumn(getPreferencesBean().getRosterTableSortColumn());
setSortAscending(getPreferencesBean().isRosterTableSortAscending());
Map enrollmentMap = getOrderedEnrollmentMapForAllItems();
List workingEnrollments = new ArrayList(enrollmentMap.keySet());
if (isEnrollmentSort()) {
return workingEnrollments;
}
Map studentIdItemIdFunctionMap = new HashMap();
Map studentIdEnrRecMap = new HashMap();
for (Iterator enrIter = workingEnrollments.iterator(); enrIter.hasNext();) {
EnrollmentRecord enr = (EnrollmentRecord) enrIter.next();
if (enr != null) {
String studentId = enr.getUser().getUserUid();
Map itemFunctionMap = (Map)enrollmentMap.get(enr);
studentIdItemIdFunctionMap.put(studentId, itemFunctionMap);
studentIdEnrRecMap.put(studentId, enr);
}
}
List rosterGradeRecords = getGradebookManager().getAllAssignmentGradeRecords(getGradebookId(), studentIdItemIdFunctionMap.keySet());
Map gradeRecordMap = new HashMap();
if (!isUserAbleToGradeAll() && isUserHasGraderPermissions()) {
getGradebookManager().addToGradeRecordMap(gradeRecordMap, rosterGradeRecords, studentIdItemIdFunctionMap);
// we need to re-sort these records b/c some may actually be null based upon permissions.
// retrieve updated grade recs from gradeRecordMap
List updatedGradeRecs = new ArrayList();
for (Iterator<Map.Entry<String, Map>> iter = gradeRecordMap.entrySet().iterator(); iter.hasNext();) {
Map.Entry<String, Map> entry = iter.next();
String studentId = entry.getKey();
Map itemIdGradeRecMap = (Map)gradeRecordMap.get(studentId);
if (!itemIdGradeRecMap.isEmpty()) {
updatedGradeRecs.addAll(itemIdGradeRecMap.values());
}
}
Collections.sort(updatedGradeRecs, AssignmentGradeRecord.calcComparator);
rosterGradeRecords = updatedGradeRecs;
} else {
getGradebookManager().addToGradeRecordMap(gradeRecordMap, rosterGradeRecords);
}
if (logger.isDebugEnabled()) logger.debug("init - gradeRecordMap.keySet().size() = " + gradeRecordMap.keySet().size());
List assignments = null;
String selectedCategoryUid = getSelectedCategoryUid();
CourseGrade courseGrade = getGradebookManager().getCourseGrade(getGradebookId());
if(selectedCategoryUid == null) {
assignments = getGradebookManager().getAssignments(getGradebookId());
} else {
assignments = getGradebookManager().getAssignmentsForCategory(new Long(getSelectedSectionFilterValue().longValue()));
}
List courseGradeRecords = getGradebookManager().getPointsEarnedCourseGradeRecords(courseGrade, studentIdItemIdFunctionMap.keySet(), assignments, gradeRecordMap);
Collections.sort(courseGradeRecords, CourseGradeRecord.calcComparator);
getGradebookManager().addToGradeRecordMap(gradeRecordMap, courseGradeRecords);
rosterGradeRecords.addAll(courseGradeRecords);
//do category results
Map categoryResultMap = new HashMap();
List categories = getGradebookManager().getCategories(getGradebookId());
getGradebookManager().addToCategoryResultMap(categoryResultMap, categories, gradeRecordMap, studentIdEnrRecMap);
if (logger.isDebugEnabled()) logger.debug("init - categoryResultMap.keySet().size() = " + categoryResultMap.keySet().size());
// Need to sort and page based on a scores column.
String sortColumn = getSortColumn();
List scoreSortedEnrollments = new ArrayList();
for(Iterator iter = rosterGradeRecords.iterator(); iter.hasNext();) {
AbstractGradeRecord agr = (AbstractGradeRecord)iter.next();
if(getColumnHeader(agr.getGradableObject()).equals(sortColumn)) {
scoreSortedEnrollments.add(studentIdEnrRecMap.get(agr.getStudentId()));
}
}
// Put enrollments with no scores at the beginning of the final list.
workingEnrollments.removeAll(scoreSortedEnrollments);
// Add all sorted enrollments with scores into the final list
workingEnrollments.addAll(scoreSortedEnrollments);
workingEnrollments = finalizeSortingAndPaging(workingEnrollments);
return workingEnrollments;
}
/**
* If we came to the instructor view from the assign details page, we need to
* set the previous and next student info according to the order and filter
* on the assign details page
* @return
*/
private List getOrderedEnrolleesFromAssignDetailsPage() {
setSortColumn(getPreferencesBean().getAssignmentDetailsTableSortColumn());
setSortAscending(getPreferencesBean().isAssignmentDetailsTableSortAscending());
List assignGradeRecords = new ArrayList();
List enrollments = new ArrayList();
Long assignmentIdAsLong = getAssignmentIdAsLong();
if (assignmentIdAsLong != null) {
Assignment prevAssignment = getGradebookManager().getAssignment(assignmentIdAsLong);
Category category = prevAssignment.getCategory();
Long catId = null;
if (category != null)
catId = category.getId();
Map enrollmentMap = getOrderedStudentIdEnrollmentMapForItem(catId);
if (isEnrollmentSort()) {
return new ArrayList(enrollmentMap.values());
}
List studentUids = new ArrayList(enrollmentMap.keySet());
if (getGradeEntryByPoints())
assignGradeRecords = getGradebookManager().getAssignmentGradeRecords(prevAssignment, studentUids);
else if (getGradeEntryByPercent() || getGradeEntryByLetter())
assignGradeRecords = getGradebookManager().getAssignmentGradeRecordsConverted(prevAssignment, studentUids);
// Need to sort and page based on a scores column.
List scoreSortedStudentUids = new ArrayList();
for(Iterator iter = assignGradeRecords.iterator(); iter.hasNext();) {
AbstractGradeRecord agr = (AbstractGradeRecord)iter.next();
scoreSortedStudentUids.add(agr.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);
if (studentUids != null) {
Iterator studentIter = studentUids.iterator();
while (studentIter.hasNext()) {
String studentId = (String) studentIter.next();
EnrollmentRecord enrollee = (EnrollmentRecord)enrollmentMap.get(studentId);
if (enrollee != null)
enrollments.add(enrollee);
}
}
}
return enrollments;
}
/**
*
* @return String representation of the student's sections/groups
*/
private String getStudentSectionsForDisplay() {
StringBuilder sectionList = new StringBuilder();
List studentMemberships = getGradebookBean().getAuthzService().getStudentSectionMembershipNames(getGradebookUid(), getStudentUid());
if (studentMemberships != null && !studentMemberships.isEmpty()) {
Collections.sort(studentMemberships);
for (int i=0; i < studentMemberships.size(); i++) {
String sectionName = (String)studentMemberships.get(i);
if (i == (studentMemberships.size()-1))
sectionList.append(sectionName);
else
sectionList.append(getLocalizedString("inst_view_sections_list", new String[] {sectionName}) + " ");
}
}
return sectionList.toString();
}
/**
* View maintenance methods.
*/
public String getReturnToPage() {
if (returnToPage == null)
returnToPage = ROSTER_PAGE;
return returnToPage;
}
public void setReturnToPage(String returnToPage) {
this.returnToPage = returnToPage;
}
public String getAssignmentId() {
return assignmentId;
}
public void setAssignmentId(String assignmentId) {
this.assignmentId = assignmentId;
}
private Long getAssignmentIdAsLong() {
Long id = null;
if (assignmentId == null)
return id;
try {
id = new Long(assignmentId);
} catch (Exception e) {
}
return id;
}
/**
* Go to assignment details page. Need to override here
* because on other pages, may need to return to where
* called from while here we want to go directly to
* assignment details.
*/
public String navigateToAssignmentDetails() {
setNav(null, null, null, "false", null);
return "assignmentDetails";
}
/**
* Go to either Roster or Assignment Details page.
*/
public String processCancel() {
if (new Boolean((String) SessionManager.getCurrentToolSession().getAttribute("middle")).booleanValue()) {
AssignmentDetailsBean assignmentDetailsBean = (AssignmentDetailsBean)FacesUtil.resolveVariable("assignmentDetailsBean");
assignmentDetailsBean.setAssignmentId(new Long(assignmentId));
return navigateToAssignmentDetails();
}
else {
return getBreadcrumbPage();
}
}
}