001: /**********************************************************************************
002: *
003: * $Id: AssignmentDetailsBean.java 29605 2007-04-26 14:01:18Z ajpoland@iupui.edu $
004: *
005: ***********************************************************************************
006: *
007: * Copyright (c) 2005 The Regents of the University of California, The MIT Corporation
008: *
009: * Licensed under the Educational Community License, Version 1.0 (the "License");
010: * you may not use this file except in compliance with the License.
011: * You may obtain a copy of the License at
012: *
013: * http://www.opensource.org/licenses/ecl1.php
014: *
015: * Unless required by applicable law or agreed to in writing, software
016: * distributed under the License is distributed on an "AS IS" BASIS,
017: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018: * See the License for the specific language governing permissions and
019: * limitations under the License.
020: *
021: **********************************************************************************/package org.sakaiproject.tool.gradebook.ui;
022:
023: import java.io.Serializable;
024: import java.util.ArrayList;
025: import java.util.Collections;
026: import java.util.HashMap;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.Map;
030: import java.util.Set;
031:
032: import javax.faces.event.ActionEvent;
033:
034: import org.apache.commons.lang.StringUtils;
035: import org.apache.commons.logging.Log;
036: import org.apache.commons.logging.LogFactory;
037: import org.sakaiproject.section.api.coursemanagement.EnrollmentRecord;
038: import org.sakaiproject.service.gradebook.shared.StaleObjectModificationException;
039: import org.sakaiproject.tool.gradebook.AbstractGradeRecord;
040: import org.sakaiproject.tool.gradebook.Assignment;
041: import org.sakaiproject.tool.gradebook.AssignmentGradeRecord;
042: import org.sakaiproject.tool.gradebook.Comment;
043: import org.sakaiproject.tool.gradebook.GradingEvent;
044: import org.sakaiproject.tool.gradebook.GradingEvents;
045: import org.sakaiproject.tool.gradebook.jsf.FacesUtil;
046:
047: public class AssignmentDetailsBean extends EnrollmentTableBean {
048: private static final Log logger = LogFactory
049: .getLog(AssignmentDetailsBean.class);
050:
051: /**
052: * The following variable keeps bean initialization from overwriting
053: * input fields from the database.
054: */
055: private boolean workInProgress;
056:
057: private List scoreRows;
058: private List updatedGradeRecords;
059: private List updatedComments;
060:
061: private Long assignmentId;
062: private Assignment assignment;
063: private Assignment previousAssignment;
064: private Assignment nextAssignment;
065:
066: private boolean isAllCommentsEditable;
067:
068: public class ScoreRow implements Serializable {
069: private AssignmentGradeRecord gradeRecord;
070: private EnrollmentRecord enrollment;
071: private Comment comment;
072: private List eventRows;
073:
074: public ScoreRow() {
075: }
076:
077: public ScoreRow(EnrollmentRecord enrollment,
078: AssignmentGradeRecord gradeRecord, Comment comment,
079: List gradingEvents) {
080: Collections.sort(gradingEvents);
081: this .enrollment = enrollment;
082: this .gradeRecord = gradeRecord;
083: this .comment = comment;
084:
085: eventRows = new ArrayList();
086: for (Iterator iter = gradingEvents.iterator(); iter
087: .hasNext();) {
088: GradingEvent gradingEvent = (GradingEvent) iter.next();
089: eventRows.add(new GradingEventRow(gradingEvent));
090: }
091: }
092:
093: public Double getScore() {
094: return gradeRecord.getPointsEarned();
095: }
096:
097: public void setScore(Double score) {
098: Double originalScore = gradeRecord.getPointsEarned();
099: if ((originalScore != null && !originalScore.equals(score))
100: || (originalScore == null && score != null)) {
101: gradeRecord.setPointsEarned(score);
102: updatedGradeRecords.add(gradeRecord);
103: }
104: }
105:
106: public EnrollmentRecord getEnrollment() {
107: return enrollment;
108: }
109:
110: public String getCommentText() {
111: return comment.getCommentText();
112: }
113:
114: public void setCommentText(String commentText) {
115: if (!StringUtils.stripToEmpty(commentText).equals(
116: StringUtils.stripToEmpty(comment.getCommentText()))) {
117: comment.setCommentText(commentText);
118: updatedComments.add(comment);
119: }
120: }
121:
122: public List getEventRows() {
123: return eventRows;
124: }
125:
126: public String getEventsLogTitle() {
127: return FacesUtil.getLocalizedString(
128: "assignment_details_log_title",
129: new String[] { enrollment.getUser()
130: .getDisplayName() });
131: }
132:
133: public boolean isCommentEditable() {
134: return (isAllCommentsEditable && !assignment
135: .isExternallyMaintained());
136: }
137: }
138:
139: protected void init() {
140: if (logger.isDebugEnabled())
141: logger.debug("loadData assignment=" + assignment
142: + ", previousAssignment=" + previousAssignment
143: + ", nextAssignment=" + nextAssignment);
144: if (logger.isDebugEnabled())
145: logger.debug("isNotValidated()=" + isNotValidated());
146: if (logger.isDebugEnabled())
147: logger.debug("workInProgress=" + workInProgress);
148: if (workInProgress) {
149: // Keeping the current form values in memory is a one-shot deal at
150: // present. The next time the user does anything, the form will be
151: // refreshed from the database.
152: workInProgress = false;
153: return;
154: }
155:
156: super .init();
157:
158: // Clear view state.
159: previousAssignment = null;
160: nextAssignment = null;
161: scoreRows = new ArrayList();
162: updatedComments = new ArrayList();
163: updatedGradeRecords = new ArrayList();
164:
165: if (assignmentId != null) {
166: assignment = getGradebookManager().getAssignmentWithStats(
167: assignmentId);
168: if (assignment != null) {
169: // Get the list of assignments. If we are sorting by mean, we
170: // need to fetch the assignment statistics as well.
171: List assignments;
172: if (Assignment.SORT_BY_MEAN
173: .equals(getAssignmentSortColumn())) {
174: assignments = getGradebookManager()
175: .getAssignmentsWithStats(getGradebookId(),
176: getAssignmentSortColumn(),
177: isAssignmentSortAscending());
178: } else {
179: assignments = getGradebookManager().getAssignments(
180: getGradebookId(),
181: getAssignmentSortColumn(),
182: isAssignmentSortAscending());
183: }
184:
185: // Set up next and previous links, if any.
186: int this Index = assignments.indexOf(assignment);
187: if (this Index > 0) {
188: previousAssignment = (Assignment) assignments
189: .get(this Index - 1);
190: }
191: if (this Index < (assignments.size() - 1)) {
192: nextAssignment = (Assignment) assignments
193: .get(this Index + 1);
194: }
195:
196: // Set up score rows.
197: Map enrollmentMap = getOrderedEnrollmentMap();
198: List studentUids = new ArrayList(enrollmentMap.keySet());
199: List gradeRecords = getGradebookManager()
200: .getAssignmentGradeRecords(assignment,
201: studentUids);
202:
203: if (!isEnrollmentSort()) {
204: // Need to sort and page based on a scores column.
205: List scoreSortedStudentUids = new ArrayList();
206: for (Iterator iter = gradeRecords.iterator(); iter
207: .hasNext();) {
208: AbstractGradeRecord agr = (AbstractGradeRecord) iter
209: .next();
210: scoreSortedStudentUids.add(agr.getStudentId());
211: }
212:
213: // Put enrollments with no scores at the beginning of the final list.
214: studentUids.removeAll(scoreSortedStudentUids);
215:
216: // Add all sorted enrollments with scores into the final list
217: studentUids.addAll(scoreSortedStudentUids);
218:
219: studentUids = finalizeSortingAndPaging(studentUids);
220: }
221:
222: // Get all of the grading events for these enrollments on this assignment
223: GradingEvents allEvents = getGradebookManager()
224: .getGradingEvents(assignment, studentUids);
225:
226: Map gradeRecordMap = new HashMap();
227: for (Iterator iter = gradeRecords.iterator(); iter
228: .hasNext();) {
229: AssignmentGradeRecord gradeRecord = (AssignmentGradeRecord) iter
230: .next();
231: if (studentUids
232: .contains(gradeRecord.getStudentId())) {
233: gradeRecordMap.put(gradeRecord.getStudentId(),
234: gradeRecord);
235: }
236: }
237:
238: // If the table is not being sorted by enrollment information, then
239: // we had to gather grade records for all students to set up the
240: // current page. In that case, eliminate the undisplayed grade records
241: // to reduce data contention.
242: if (!isEnrollmentSort()) {
243: gradeRecords = new ArrayList(gradeRecordMap
244: .values());
245: }
246:
247: // Get all of the comments for these enrollments on this assignment.
248: List comments = getGradebookManager().getComments(
249: assignment, studentUids);
250: Map commentMap = new HashMap();
251: for (Iterator iter = comments.iterator(); iter
252: .hasNext();) {
253: Comment comment = (Comment) iter.next();
254: commentMap.put(comment.getStudentId(), comment);
255: }
256:
257: for (Iterator iter = studentUids.iterator(); iter
258: .hasNext();) {
259: String studentUid = (String) iter.next();
260: EnrollmentRecord enrollment = (EnrollmentRecord) enrollmentMap
261: .get(studentUid);
262: AssignmentGradeRecord gradeRecord = (AssignmentGradeRecord) gradeRecordMap
263: .get(studentUid);
264: if (gradeRecord == null) {
265: gradeRecord = new AssignmentGradeRecord(
266: assignment, studentUid, null);
267: gradeRecords.add(gradeRecord);
268: }
269: Comment comment = (Comment) commentMap
270: .get(studentUid);
271: if (comment == null) {
272: comment = new Comment(studentUid, null,
273: assignment);
274: }
275:
276: scoreRows.add(new ScoreRow(enrollment, gradeRecord,
277: comment, allEvents.getEvents(studentUid)));
278: }
279:
280: } else {
281: // The assignment might have been removed since this link was set up.
282: if (logger.isWarnEnabled())
283: logger.warn("No assignmentId=" + assignmentId
284: + " in gradebookUid " + getGradebookUid());
285: FacesUtil
286: .addErrorMessage(getLocalizedString("assignment_details_assignment_removed"));
287: }
288: }
289: }
290:
291: // Delegated sort methods for read-only assignment sort order
292: public String getAssignmentSortColumn() {
293: return getPreferencesBean().getAssignmentSortColumn();
294: }
295:
296: public boolean isAssignmentSortAscending() {
297: return getPreferencesBean().isAssignmentSortAscending();
298: }
299:
300: /**
301: * Action listener to view a different assignment.
302: */
303: public void processAssignmentIdChange(ActionEvent event) {
304: Map params = FacesUtil.getEventParameterMap(event);
305: if (logger.isDebugEnabled())
306: logger.debug("processAssignmentIdAction params=" + params
307: + ", current assignmentId=" + assignmentId);
308: Long idParam = (Long) params.get("assignmentId");
309: if (idParam != null) {
310: setAssignmentId(idParam);
311: }
312: }
313:
314: /**
315: * Action listener to update scores.
316: */
317: public void processUpdateScores(ActionEvent event) {
318: try {
319: saveScores();
320: } catch (StaleObjectModificationException e) {
321: FacesUtil
322: .addErrorMessage(getLocalizedString("assignment_details_locking_failure"));
323: }
324: }
325:
326: private void saveScores() throws StaleObjectModificationException {
327: if (logger.isInfoEnabled())
328: logger.info("saveScores " + assignmentId);
329:
330: Set excessiveScores = getGradebookManager()
331: .updateAssignmentGradesAndComments(assignment,
332: updatedGradeRecords, updatedComments);
333:
334: if (logger.isDebugEnabled())
335: logger.debug("About to save " + updatedComments.size()
336: + " updated comments");
337: if (updatedGradeRecords.size() > 0) {
338: getGradebookBean().getEventTrackingService().postEvent(
339: "gradebook.updateItemScores",
340: "/gradebook/" + getGradebookId() + "/"
341: + updatedGradeRecords.size() + "/"
342: + getAuthzLevel());
343: }
344: if (updatedComments.size() > 0) {
345: getGradebookBean().getEventTrackingService().postEvent(
346: "gradebook.comment",
347: "/gradebook/" + getGradebookId() + "/"
348: + updatedComments.size() + "/"
349: + getAuthzLevel());
350: }
351: String messageKey = (excessiveScores.size() > 0) ? "assignment_details_scores_saved_excessive"
352: : "assignment_details_scores_saved";
353:
354: // Let the user know.
355: FacesUtil.addMessage(getLocalizedString(messageKey));
356: }
357:
358: public void toggleEditableComments(ActionEvent event) {
359: // Don't write over any scores the user entered before pressing
360: // the "Edit Comments" button.
361: if (!isAllCommentsEditable) {
362: workInProgress = true;
363: }
364:
365: isAllCommentsEditable = !isAllCommentsEditable;
366: }
367:
368: /**
369: * View maintenance methods.
370: */
371: public Long getAssignmentId() {
372: if (logger.isDebugEnabled())
373: logger.debug("getAssignmentId " + assignmentId);
374: return assignmentId;
375: }
376:
377: public void setAssignmentId(Long assignmentId) {
378: if (logger.isDebugEnabled())
379: logger.debug("setAssignmentId " + assignmentId);
380: this .assignmentId = assignmentId;
381: }
382:
383: /**
384: * In IE (but not Mozilla/Firefox) empty request parameters may be returned
385: * to JSF as the string "null". JSF always "restores" some idea of the
386: * last view, even if that idea is always going to be null because a redirect
387: * has occurred. Put these two things together, and you end up with
388: * a class cast exception when redirecting from this request-scoped
389: * bean to a static page.
390: */
391: public void setAssignmentIdParam(String assignmentIdParam) {
392: if (logger.isDebugEnabled())
393: logger.debug("setAssignmentIdParam String "
394: + assignmentIdParam);
395: if ((assignmentIdParam != null)
396: && (assignmentIdParam.length() > 0)
397: && !assignmentIdParam.equals("null")) {
398: try {
399: setAssignmentId(Long.valueOf(assignmentIdParam));
400: } catch (NumberFormatException e) {
401: if (logger.isWarnEnabled())
402: logger
403: .warn("AssignmentId param set to non-number '"
404: + assignmentIdParam + "'");
405: }
406: }
407: }
408:
409: public boolean isFirst() {
410: return (previousAssignment == null);
411: }
412:
413: public String getPreviousTitle() {
414: return (previousAssignment != null) ? previousAssignment
415: .getName() : "";
416: }
417:
418: public boolean isLast() {
419: return (nextAssignment == null);
420: }
421:
422: public String getNextTitle() {
423: return (nextAssignment != null) ? nextAssignment.getName() : "";
424: }
425:
426: public List getScoreRows() {
427: return scoreRows;
428: }
429:
430: public void setScoreRows(List scoreRows) {
431: this .scoreRows = scoreRows;
432: }
433:
434: // A desparate stab at reasonable embedded validation message formatting.
435: // If the score column is an input box, it may have a wide message associated
436: // with it, and we want the input field left-aligned to match up with
437: // the non-erroroneous input fields (even though the actual input values
438: // will be right-aligned). On the other hand, if the score column is read-only,
439: // then we want to simply right-align the table column.
440: public String getScoreColumnAlignment() {
441: if (assignment.isExternallyMaintained()) {
442: return "right";
443: } else {
444: return "left";
445: }
446: }
447:
448: public String getEventsLogType() {
449: return FacesUtil
450: .getLocalizedString("assignment_details_log_type");
451: }
452:
453: // Sorting
454: public boolean isSortAscending() {
455: return getPreferencesBean()
456: .isAssignmentDetailsTableSortAscending();
457: }
458:
459: public void setSortAscending(boolean sortAscending) {
460: getPreferencesBean().setAssignmentDetailsTableSortAscending(
461: sortAscending);
462: }
463:
464: public String getSortColumn() {
465: return getPreferencesBean()
466: .getAssignmentDetailsTableSortColumn();
467: }
468:
469: public void setSortColumn(String sortColumn) {
470: getPreferencesBean().setAssignmentDetailsTableSortColumn(
471: sortColumn);
472: }
473:
474: public Assignment getAssignment() {
475: return assignment;
476: }
477:
478: public void setAssignment(Assignment assignment) {
479: this .assignment = assignment;
480: }
481:
482: public Assignment getNextAssignment() {
483: return nextAssignment;
484: }
485:
486: public void setNextAssignment(Assignment nextAssignment) {
487: this .nextAssignment = nextAssignment;
488: }
489:
490: public Assignment getPreviousAssignment() {
491: return previousAssignment;
492: }
493:
494: public void setPreviousAssignment(Assignment previousAssignment) {
495: this .previousAssignment = previousAssignment;
496: }
497:
498: public String getCommentsToggle() {
499: String messageKey = isAllCommentsEditable ? "assignment_details_comments_read"
500: : "assignment_details_comments_edit";
501: return getLocalizedString(messageKey);
502: }
503:
504: public boolean isAllCommentsEditable() {
505: return isAllCommentsEditable;
506: }
507: }
|