001: /**********************************************************************************
002: *
003: * $Id: FeedbackOptionsBean.java 27835 2007-03-26 23:45:15Z ray@media.berkeley.edu $
004: *
005: ***********************************************************************************
006: *
007: * Copyright (c) 2005, 2006 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.Iterator;
027: import java.util.List;
028:
029: import javax.faces.event.ActionEvent;
030: import javax.faces.model.SelectItem;
031:
032: import org.apache.commons.logging.Log;
033: import org.apache.commons.logging.LogFactory;
034: import org.sakaiproject.service.gradebook.shared.StaleObjectModificationException;
035: import org.sakaiproject.tool.gradebook.GradeMapping;
036: import org.sakaiproject.tool.gradebook.Gradebook;
037: import org.sakaiproject.tool.gradebook.jsf.FacesUtil;
038:
039: /**
040: * Provides support for the student feedback options page, which also controls
041: * grade-to-percentage mappings for the gradebook.
042: */
043: public class FeedbackOptionsBean extends GradebookDependentBean
044: implements Serializable {
045: private static final Log log = LogFactory
046: .getLog(FeedbackOptionsBean.class);
047:
048: // View maintenance fields - serializable.
049:
050: /**
051: * This is the one page in which the user can change what's on the screen
052: * and have their current working inputs remembered without updating the
053: * database. In other words, this is currently our only "what if?" workflow.
054: * The following variable keeps bean initialization from overwriting stable
055: * input fields from the database.
056: */
057: private boolean workInProgress;
058:
059: /** Cache a copy of the gradebook object in the request thread, to keep track of all grade mapping changes */
060: private Gradebook localGradebook;
061:
062: private Long selectedGradeMappingId;
063:
064: /** The list of select box items */
065: private List gradeMappingsSelectItems;
066:
067: // View into row-specific data.
068: private List gradeRows;
069:
070: public class GradeRow implements Serializable {
071: private GradeMapping gradeMapping;
072: private String grade;
073: private boolean editable;
074:
075: public GradeRow() {
076: }
077:
078: public GradeRow(GradeMapping gradeMapping, String grade,
079: boolean editable) {
080: this .gradeMapping = gradeMapping;
081: this .grade = grade;
082: this .editable = editable;
083: }
084:
085: public String getGrade() {
086: return grade;
087: }
088:
089: public Double getMappingValue() {
090: return (Double) gradeMapping.getGradeMap().get(grade);
091: }
092:
093: public void setMappingValue(Double value) {
094: gradeMapping.getGradeMap().put(grade, value);
095: }
096:
097: public boolean isGradeEditable() {
098: return editable;
099: }
100: }
101:
102: public Gradebook getLocalGradebook() {
103: return localGradebook;
104: }
105:
106: /**
107: * Initializes this backing bean.
108: */
109: protected void init() {
110: if (!workInProgress) {
111: localGradebook = getGradebookManager()
112: .getGradebookWithGradeMappings(getGradebookId());
113:
114: // Load the grade mappings, sorted by name.
115: List gradeMappings = new ArrayList(localGradebook
116: .getGradeMappings());
117: Collections.sort(gradeMappings);
118:
119: // Create the grade type drop-down menu
120: gradeMappingsSelectItems = new ArrayList(gradeMappings
121: .size());
122: for (Iterator iter = gradeMappings.iterator(); iter
123: .hasNext();) {
124: GradeMapping gradeMapping = (GradeMapping) iter.next();
125: gradeMappingsSelectItems.add(new SelectItem(
126: gradeMapping.getId(), gradeMapping.getName()));
127: }
128: // set the selected grade mapping
129: GradeMapping selectedGradeMapping = localGradebook
130: .getSelectedGradeMapping();
131: selectedGradeMappingId = selectedGradeMapping.getId();
132: initGradeRows();
133: }
134:
135: // Set the view state.
136: workInProgress = true;
137: }
138:
139: private void initGradeRows() {
140: // Set up UI table view.
141: gradeRows = new ArrayList();
142: GradeMapping selectedGradeMapping = localGradebook
143: .getSelectedGradeMapping();
144: for (Iterator iter = selectedGradeMapping.getGrades()
145: .iterator(); iter.hasNext();) {
146: String grade = (String) iter.next();
147:
148: // Bottom grades (with a lower bound of 0%) and manual-only
149: // grades (which have no percentage equivalent) are not
150: // editable.
151: Double d = selectedGradeMapping.getDefaultBottomPercents()
152: .get(grade);
153: boolean editable = ((d != null) && (d.doubleValue() > 0.0));
154: gradeRows.add(new GradeRow(selectedGradeMapping, grade,
155: editable));
156: }
157: }
158:
159: public List getGradeRows() {
160: return gradeRows;
161: }
162:
163: public void setGradeRows(List gradeRows) {
164: this .gradeRows = gradeRows;
165: }
166:
167: /**
168: * Action listener to view a different grade type mapping.
169: * According to the specification, we do not update any changed values in the currently
170: * shown mapping, but we do remember them.
171: */
172: public void changeGradeType(ActionEvent event) {
173: for (Iterator iter = localGradebook.getGradeMappings()
174: .iterator(); iter.hasNext();) {
175: GradeMapping mapping = (GradeMapping) iter.next();
176: if (mapping.getId().equals(selectedGradeMappingId)) {
177: localGradebook.setSelectedGradeMapping(mapping);
178: initGradeRows();
179: }
180: }
181: }
182:
183: /**
184: * Action listener to reset the currently selected grade mapping to its default values.
185: * Other, not currently visible, changed unsaved grade mapping settings are left as they
186: * are.
187: */
188: public void resetMappingValues(ActionEvent event) {
189: localGradebook.getSelectedGradeMapping().setDefaultValues();
190: }
191:
192: /**
193: * Updates the gradebook to reflect the currently selected grade type and mapping.
194: */
195: public String save() {
196: if (!isMappingValid(localGradebook.getSelectedGradeMapping())) {
197: return null;
198: }
199:
200: try {
201: getGradebookManager().updateGradebook(localGradebook);
202: FacesUtil
203: .addRedirectSafeMessage(getLocalizedString("feedback_options_submit_success"));
204: } catch (IllegalStateException ise) {
205: FacesUtil.addErrorMessage(getLocalizedString(
206: "feedback_options_illegal_change",
207: new String[] { localGradebook
208: .getSelectedGradeMapping().getName() }));
209: return null;
210: } catch (StaleObjectModificationException e) {
211: log.error(e);
212: FacesUtil
213: .addErrorMessage(getLocalizedString("feedback_options_locking_failure"));
214: return null;
215: }
216:
217: return "overview";
218: }
219:
220: private boolean isMappingValid(GradeMapping gradeMapping) {
221: boolean valid = true;
222: Double previousPercentage = null;
223: for (Iterator iter = gradeMapping.getGrades().iterator(); iter
224: .hasNext();) {
225: String grade = (String) iter.next();
226: Double percentage = (Double) gradeMapping.getValue(grade);
227: if (log.isDebugEnabled())
228: log.debug("checking percentage " + percentage
229: + " for validity");
230:
231: // Grades that are percentage-based need to remain percentage-based,
232: // be in descending order, and end with 0.
233: // Manual-only grades (which aren't associated with a percentage) always
234: // follow the lowest percentage-based grade, and must stay manual-only.
235: boolean manualOnly = (gradeMapping
236: .getDefaultBottomPercents().get(grade) == null);
237:
238: if (manualOnly) {
239: if (percentage != null) {
240: // This shouldn't happen, given the UI.
241: if (log.isErrorEnabled())
242: log
243: .error("User "
244: + getUserUid()
245: + " attempted to set manual-only grade '"
246: + grade + "' to be worth "
247: + percentage + " percent");
248: percentage = null;
249: valid = false;
250: }
251: } else {
252: if (percentage == null) {
253: FacesUtil
254: .addUniqueErrorMessage(getLocalizedString("feedback_options_require_all_values"));
255: valid = false;
256: } else if (percentage.doubleValue() < 0) {
257: FacesUtil
258: .addUniqueErrorMessage(getLocalizedString("feedback_options_require_positive"));
259: valid = false;
260: } else if ((previousPercentage != null)
261: && (previousPercentage.doubleValue() < percentage
262: .doubleValue())) {
263: FacesUtil
264: .addUniqueErrorMessage(getLocalizedString("feedback_options_require_descending_order"));
265: valid = false;
266: }
267: }
268: previousPercentage = percentage;
269: }
270: return valid;
271: }
272:
273: public String cancel() {
274: // Just in case we change the navigation to stay on this page,
275: // clear the work-in-progress indicator so that the user can
276: // start fresh.
277: workInProgress = false;
278:
279: return "overview";
280: }
281:
282: public List getGradeMappingsSelectItems() {
283: return gradeMappingsSelectItems;
284: }
285:
286: public void setGradeMappingsSelectItems(
287: List gradeMappingsSelectItems) {
288: this .gradeMappingsSelectItems = gradeMappingsSelectItems;
289: }
290:
291: public Long getSelectedGradeMappingId() {
292: return selectedGradeMappingId;
293: }
294:
295: public void setSelectedGradeMappingId(Long selectedGradeMappingId) {
296: this.selectedGradeMappingId = selectedGradeMappingId;
297: }
298: }
|