001: /**********************************************************************************
002: *
003: * $Id: RosterBean.java 22226 2007-03-06 17:42:53Z ray@media.berkeley.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.HashSet;
028: import java.util.Iterator;
029: import java.util.List;
030: import java.util.Map;
031: import java.util.Set;
032:
033: import javax.faces.application.Application;
034: import javax.faces.component.UIColumn;
035: import javax.faces.component.html.HtmlOutputText;
036: import javax.faces.context.FacesContext;
037: import javax.faces.event.ActionEvent;
038:
039: import org.apache.commons.logging.Log;
040: import org.apache.commons.logging.LogFactory;
041: import org.apache.myfaces.component.html.ext.HtmlDataTable;
042: import org.apache.myfaces.custom.sortheader.HtmlCommandSortHeader;
043: import org.sakaiproject.jsf.spreadsheet.SpreadsheetDataFileWriterCsv;
044: import org.sakaiproject.jsf.spreadsheet.SpreadsheetDataFileWriterXls;
045: import org.sakaiproject.jsf.spreadsheet.SpreadsheetUtil;
046: import org.sakaiproject.section.api.coursemanagement.EnrollmentRecord;
047: import org.sakaiproject.section.api.coursemanagement.User;
048: import org.sakaiproject.tool.gradebook.AbstractGradeRecord;
049: import org.sakaiproject.tool.gradebook.Assignment;
050: import org.sakaiproject.tool.gradebook.CourseGrade;
051: import org.sakaiproject.tool.gradebook.CourseGradeRecord;
052: import org.sakaiproject.tool.gradebook.GradableObject;
053: import org.sakaiproject.tool.gradebook.jsf.AssignmentPointsConverter;
054:
055: /**
056: * Backing bean for the visible list of assignments in the gradebook.
057: */
058: public class RosterBean extends EnrollmentTableBean implements
059: Serializable, Paging {
060: private static final Log logger = LogFactory
061: .getLog(RosterBean.class);
062:
063: // Used to generate IDs for the dynamically created assignment columns.
064: private static final String ASSIGNMENT_COLUMN_PREFIX = "asg_";
065:
066: // View maintenance fields - serializable.
067: private List gradableObjectColumns; // Needed to build table columns
068: private List workingEnrollments;
069:
070: public class GradableObjectColumn implements Serializable {
071: private Long id;
072: private String name;
073:
074: public GradableObjectColumn() {
075: }
076:
077: public GradableObjectColumn(GradableObject gradableObject) {
078: id = gradableObject.getId();
079: name = getColumnHeader(gradableObject);
080: }
081:
082: public Long getId() {
083: return id;
084: }
085:
086: public void setId(Long id) {
087: this .id = id;
088: }
089:
090: public String getName() {
091: return name;
092: }
093:
094: public void setName(String name) {
095: this .name = name;
096: }
097: }
098:
099: // Controller fields - transient.
100: private transient List studentRows;
101: private transient Map gradeRecordMap;
102:
103: public class StudentRow implements Serializable {
104: private EnrollmentRecord enrollment;
105:
106: public StudentRow() {
107: }
108:
109: public StudentRow(EnrollmentRecord enrollment) {
110: this .enrollment = enrollment;
111: }
112:
113: public String getStudentUid() {
114: return enrollment.getUser().getUserUid();
115: }
116:
117: public String getSortName() {
118: return enrollment.getUser().getSortName();
119: }
120:
121: public String getDisplayId() {
122: return enrollment.getUser().getDisplayId();
123: }
124:
125: public Map getScores() {
126: return (Map) gradeRecordMap.get(enrollment.getUser()
127: .getUserUid());
128: }
129: }
130:
131: protected void init() {
132: super .init();
133:
134: List assignments = getGradebookManager().getAssignments(
135: getGradebookId());
136: CourseGrade courseGrade = getGradebookManager().getCourseGrade(
137: getGradebookId());
138: gradableObjectColumns = new ArrayList();
139: for (Iterator iter = assignments.iterator(); iter.hasNext();) {
140: gradableObjectColumns.add(new GradableObjectColumn(
141: (GradableObject) iter.next()));
142: }
143: gradableObjectColumns
144: .add(new GradableObjectColumn(courseGrade));
145:
146: Map enrollmentMap = getOrderedEnrollmentMap();
147:
148: List gradeRecords = getGradebookManager()
149: .getAllAssignmentGradeRecords(getGradebookId(),
150: enrollmentMap.keySet());
151: workingEnrollments = new ArrayList(enrollmentMap.values());
152:
153: gradeRecordMap = new HashMap();
154: getGradebookManager().addToGradeRecordMap(gradeRecordMap,
155: gradeRecords);
156: if (logger.isDebugEnabled())
157: logger.debug("init - gradeRecordMap.keySet().size() = "
158: + gradeRecordMap.keySet().size());
159:
160: List courseGradeRecords = getGradebookManager()
161: .getPointsEarnedCourseGradeRecords(courseGrade,
162: enrollmentMap.keySet(), assignments,
163: gradeRecordMap);
164: Collections.sort(courseGradeRecords,
165: CourseGradeRecord.calcComparator);
166: getGradebookManager().addToGradeRecordMap(gradeRecordMap,
167: courseGradeRecords);
168: gradeRecords.addAll(courseGradeRecords);
169:
170: if (!isEnrollmentSort()) {
171: // Need to sort and page based on a scores column.
172: String sortColumn = getSortColumn();
173: List scoreSortedEnrollments = new ArrayList();
174: for (Iterator iter = gradeRecords.iterator(); iter
175: .hasNext();) {
176: AbstractGradeRecord agr = (AbstractGradeRecord) iter
177: .next();
178: if (getColumnHeader(agr.getGradableObject()).equals(
179: sortColumn)) {
180: scoreSortedEnrollments.add(enrollmentMap.get(agr
181: .getStudentId()));
182: }
183: }
184:
185: // Put enrollments with no scores at the beginning of the final list.
186: workingEnrollments.removeAll(scoreSortedEnrollments);
187:
188: // Add all sorted enrollments with scores into the final list
189: workingEnrollments.addAll(scoreSortedEnrollments);
190:
191: workingEnrollments = finalizeSortingAndPaging(workingEnrollments);
192: }
193:
194: studentRows = new ArrayList(workingEnrollments.size());
195: for (Iterator iter = workingEnrollments.iterator(); iter
196: .hasNext();) {
197: EnrollmentRecord enrollment = (EnrollmentRecord) iter
198: .next();
199: studentRows.add(new StudentRow(enrollment));
200: }
201:
202: }
203:
204: private String getColumnHeader(GradableObject gradableObject) {
205: if (gradableObject.isCourseGrade()) {
206: return getLocalizedString("roster_course_grade_column_name");
207: } else {
208: return ((Assignment) gradableObject).getName();
209: }
210: }
211:
212: // The roster table uses assignments as columns, and therefore the component
213: // model needs to have those columns added dynamically, based on the current
214: // state of the gradebook.
215: // In JSF 1.1, dynamic data table columns are managed by binding the component
216: // tag to a bean property.
217:
218: // It's not exactly intuitive, but the convention is for the bean to return
219: // null, so that JSF can create and manage the UIData component itself.
220: public HtmlDataTable getRosterDataTable() {
221: if (logger.isDebugEnabled())
222: logger.debug("getRosterDataTable");
223: return null;
224: }
225:
226: public void setRosterDataTable(HtmlDataTable rosterDataTable) {
227: if (logger.isDebugEnabled()) {
228: logger.debug("setRosterDataTable gradableObjectColumns="
229: + gradableObjectColumns + ", rosterDataTable="
230: + rosterDataTable);
231: if (rosterDataTable != null) {
232: logger.debug(" data children="
233: + rosterDataTable.getChildren());
234: }
235: }
236:
237: // Set the columnClasses on the data table
238: StringBuffer colClasses = new StringBuffer("left,left,");
239: for (Iterator iter = gradableObjectColumns.iterator(); iter
240: .hasNext();) {
241: iter.next();
242: colClasses.append("center");
243: if (iter.hasNext()) {
244: colClasses.append(",");
245: }
246: }
247: rosterDataTable.setColumnClasses(colClasses.toString());
248:
249: if (rosterDataTable.findComponent(ASSIGNMENT_COLUMN_PREFIX
250: + "0") == null) {
251: Application app = FacesContext.getCurrentInstance()
252: .getApplication();
253:
254: // Add columns for each assignment. Be sure to create unique IDs
255: // for all child components.
256: int colpos = 0;
257: for (Iterator iter = gradableObjectColumns.iterator(); iter
258: .hasNext(); colpos++) {
259: GradableObjectColumn columnData = (GradableObjectColumn) iter
260: .next();
261:
262: UIColumn col = new UIColumn();
263: col.setId(ASSIGNMENT_COLUMN_PREFIX + colpos);
264:
265: HtmlCommandSortHeader sortHeader = new HtmlCommandSortHeader();
266: sortHeader.setId(ASSIGNMENT_COLUMN_PREFIX + "sorthdr_"
267: + colpos);
268: sortHeader
269: .setRendererType("org.apache.myfaces.SortHeader"); // Yes, this is necessary.
270: sortHeader.setArrow(true);
271: sortHeader.setColumnName(columnData.getName());
272: sortHeader.setActionListener(app.createMethodBinding(
273: "#{rosterBean.sort}",
274: new Class[] { ActionEvent.class }));
275:
276: // Allow word-wrapping on assignment name columns.
277: sortHeader.setStyleClass("allowWrap");
278:
279: HtmlOutputText headerText = new HtmlOutputText();
280: headerText.setId(ASSIGNMENT_COLUMN_PREFIX + "hdr_"
281: + colpos);
282: // Try straight setValue rather than setValueBinding.
283: headerText.setValue(columnData.getName());
284:
285: sortHeader.getChildren().add(headerText);
286: col.setHeader(sortHeader);
287:
288: HtmlOutputText contents = new HtmlOutputText();
289: contents.setEscape(false);
290: contents.setId(ASSIGNMENT_COLUMN_PREFIX + "cell_"
291: + colpos);
292: contents
293: .setValueBinding(
294: "value",
295: app
296: .createValueBinding("#{row.scores[rosterBean.gradableObjectColumns["
297: + colpos + "].id]}"));
298: contents.setConverter(new AssignmentPointsConverter());
299:
300: // Distinguish the "Cumulative" score for the course, which, by convention,
301: // is always the last column.
302: if (!iter.hasNext()) {
303: contents.setStyleClass("courseGrade");
304: }
305:
306: col.getChildren().add(contents);
307:
308: rosterDataTable.getChildren().add(col);
309: }
310: }
311: }
312:
313: public List getGradableObjectColumns() {
314: return gradableObjectColumns;
315: }
316:
317: public void setGradableObjectColumns(List gradableObjectColumns) {
318: this .gradableObjectColumns = gradableObjectColumns;
319: }
320:
321: public List getStudentRows() {
322: return studentRows;
323: }
324:
325: // Sorting
326: public boolean isSortAscending() {
327: return getPreferencesBean().isRosterTableSortAscending();
328: }
329:
330: public void setSortAscending(boolean sortAscending) {
331: getPreferencesBean().setRosterTableSortAscending(sortAscending);
332: }
333:
334: public String getSortColumn() {
335: return getPreferencesBean().getRosterTableSortColumn();
336: }
337:
338: public void setSortColumn(String sortColumn) {
339: getPreferencesBean().setRosterTableSortColumn(sortColumn);
340: }
341:
342: public void exportCsv(ActionEvent event) {
343: if (logger.isInfoEnabled())
344: logger.info("exporting roster as CSV for gradebook "
345: + getGradebookUid());
346: getGradebookBean().getEventTrackingService().postEvent(
347: "gradebook.downloadRoster",
348: "/gradebook/" + getGradebookId() + "/"
349: + getAuthzLevel());
350: SpreadsheetUtil
351: .downloadSpreadsheetData(
352: getSpreadsheetData(),
353: getDownloadFileName(getLocalizedString("export_gradebook_prefix")),
354: new SpreadsheetDataFileWriterCsv());
355: }
356:
357: public void exportExcel(ActionEvent event) {
358: if (logger.isInfoEnabled())
359: logger.info("exporting roster as Excel for gradebook "
360: + getGradebookUid());
361: String authzLevel = (getGradebookBean().getAuthzService()
362: .isUserAbleToGradeAll(getGradebookUid())) ? "instructor"
363: : "TA";
364: getGradebookBean().getEventTrackingService().postEvent(
365: "gradebook.downloadRoster",
366: "/gradebook/" + getGradebookId() + "/"
367: + getAuthzLevel());
368: SpreadsheetUtil
369: .downloadSpreadsheetData(
370: getSpreadsheetData(),
371: getDownloadFileName(getLocalizedString("export_gradebook_prefix")),
372: new SpreadsheetDataFileWriterXls());
373: }
374:
375: private List<List<Object>> getSpreadsheetData() {
376: // Get the full list of filtered enrollments and scores (not just the current page's worth).
377: List filteredEnrollments = getWorkingEnrollments();
378: Collections.sort(filteredEnrollments,
379: ENROLLMENT_NAME_COMPARATOR);
380: Set<String> studentUids = new HashSet<String>();
381: for (Iterator iter = filteredEnrollments.iterator(); iter
382: .hasNext();) {
383: EnrollmentRecord enrollment = (EnrollmentRecord) iter
384: .next();
385: studentUids.add(enrollment.getUser().getUserUid());
386: }
387:
388: Map filteredGradesMap = new HashMap();
389: List gradeRecords = getGradebookManager()
390: .getAllAssignmentGradeRecords(getGradebookId(),
391: studentUids);
392: getGradebookManager().addToGradeRecordMap(filteredGradesMap,
393: gradeRecords);
394:
395: List gradableObjects = getGradebookManager().getAssignments(
396: getGradebookId());
397: CourseGrade courseGrade = getGradebookManager().getCourseGrade(
398: getGradebookId());
399: List courseGradeRecords = getGradebookManager()
400: .getPointsEarnedCourseGradeRecords(courseGrade,
401: studentUids, gradableObjects, filteredGradesMap);
402: getGradebookManager().addToGradeRecordMap(filteredGradesMap,
403: courseGradeRecords);
404: gradableObjects.add(courseGrade);
405: return getSpreadsheetData(filteredEnrollments,
406: filteredGradesMap, gradableObjects);
407: }
408:
409: private List<List<Object>> getSpreadsheetData(List enrollments,
410: Map gradesMap, List gradableObjects) {
411: List<List<Object>> spreadsheetData = new ArrayList<List<Object>>();
412:
413: // Build column headers.
414: List<Object> headerRow = new ArrayList<Object>();
415: headerRow.add(getLocalizedString("export_student_id"));
416: headerRow.add(getLocalizedString("export_student_name"));
417: for (Object gradableObject : gradableObjects) {
418: String colName = null;
419: if (gradableObject instanceof Assignment) {
420: colName = ((Assignment) gradableObject).getName();
421: } else if (gradableObject instanceof CourseGrade) {
422: colName = getLocalizedString("roster_course_grade_column_name");
423: }
424: headerRow.add(colName);
425: }
426: spreadsheetData.add(headerRow);
427:
428: // Build student score rows.
429: for (Object enrollment : enrollments) {
430: User student = ((EnrollmentRecord) enrollment).getUser();
431: String studentUid = student.getUserUid();
432: Map studentMap = (Map) gradesMap.get(studentUid);
433: List<Object> row = new ArrayList<Object>();
434: row.add(student.getDisplayId());
435: row.add(student.getSortName());
436: for (Object gradableObject : gradableObjects) {
437: Double score = null;
438: if (studentMap != null) {
439: AbstractGradeRecord gradeRecord = (AbstractGradeRecord) studentMap
440: .get(((GradableObject) gradableObject)
441: .getId());
442: if (gradeRecord != null) {
443: score = gradeRecord.getPointsEarned();
444: }
445: }
446: row.add(score);
447: }
448: spreadsheetData.add(row);
449: }
450:
451: return spreadsheetData;
452: }
453: }
|