/**********************************************************************************
*
* $Id: FeedbackOptionsBean.java 105079 2012-02-24 23:08:11Z ottenhoff@longsight.com $
*
***********************************************************************************
*
* 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.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.faces.event.ActionEvent;
import javax.faces.model.SelectItem;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.service.gradebook.shared.StaleObjectModificationException;
import org.sakaiproject.tool.gradebook.GradeMapping;
import org.sakaiproject.tool.gradebook.Gradebook;
import org.sakaiproject.tool.gradebook.jsf.FacesUtil;
import org.sakaiproject.service.gradebook.shared.GradebookService;
/**
* Provides support for the student feedback options page, which also controls
* grade-to-percentage mappings for the gradebook.
*/
public class FeedbackOptionsBean extends GradebookDependentBean implements Serializable {
private static final Log log = LogFactory.getLog(FeedbackOptionsBean.class);
// View maintenance fields - serializable.
/**
* This is the one page in which the user can change what's on the screen
* and have their current working inputs remembered without updating the
* database. In other words, this is currently our only "what if?" workflow.
* The following variable keeps bean initialization from overwriting stable
* input fields from the database.
*/
private boolean workInProgress;
/** Cache a copy of the gradebook object in the request thread, to keep track of all grade mapping changes */
private Gradebook localGradebook;
private Long selectedGradeMappingId;
/** The list of select box items */
private List gradeMappingsSelectItems;
// View into row-specific data.
private List gradeRows;
private boolean isValidWithLetterGrade = true;
public class GradeRow implements Serializable {
private GradeMapping gradeMapping;
private String grade;
private boolean editable;
public GradeRow() {
}
public GradeRow(GradeMapping gradeMapping, String grade, boolean editable) {
this.gradeMapping = gradeMapping;
this.grade = grade;
this.editable = editable;
}
public String getGrade() {
return grade;
}
public Double getMappingValue() {
return (Double)gradeMapping.getGradeMap().get(grade);
}
public void setMappingValue(Double value) {
gradeMapping.getGradeMap().put(grade, value);
}
public boolean isGradeEditable() {
return editable;
}
}
public Gradebook getLocalGradebook() {
return localGradebook;
}
/**
* Initializes this backing bean.
*/
protected void init() {
if (!workInProgress) {
localGradebook = getGradebookManager().getGradebookWithGradeMappings(getGradebookId());
// Load the grade mappings, sorted by name.
List gradeMappings = new ArrayList(localGradebook.getGradeMappings());
Collections.sort(gradeMappings);
// Create the grade type drop-down menu
gradeMappingsSelectItems = new ArrayList(gradeMappings.size());
for (Iterator iter = gradeMappings.iterator(); iter.hasNext(); ) {
GradeMapping gradeMapping = (GradeMapping)iter.next();
gradeMappingsSelectItems.add(new SelectItem(gradeMapping.getId(), gradeMapping.getName()));
}
// set the selected grade mapping
GradeMapping selectedGradeMapping = localGradebook.getSelectedGradeMapping();
selectedGradeMappingId = selectedGradeMapping.getId();
initGradeRows();
}
// Set the view state.
workInProgress = true;
}
private void initGradeRows() {
// Set up UI table view.
gradeRows = new ArrayList();
GradeMapping selectedGradeMapping = localGradebook.getSelectedGradeMapping();
for (Iterator iter = selectedGradeMapping.getGrades().iterator(); iter.hasNext(); ) {
String grade = (String)iter.next();
// Bottom grades (with a lower bound of 0%) and manual-only
// grades (which have no percentage equivalent) are not
// editable.
Double d = selectedGradeMapping.getDefaultBottomPercents().get(grade);
boolean editable = ((d != null) && (d.doubleValue() > 0.0));
gradeRows.add(new GradeRow(selectedGradeMapping, grade, editable));
}
}
public List getGradeRows() {
return gradeRows;
}
public void setGradeRows(List gradeRows) {
this.gradeRows = gradeRows;
}
/**
* Action listener to view a different grade type mapping.
* According to the specification, we do not update any changed values in the currently
* shown mapping, but we do remember them.
*/
public void changeGradeType(ActionEvent event) {
isValidWithLetterGrade = true;
for(Iterator iter = localGradebook.getGradeMappings().iterator(); iter.hasNext();) {
GradeMapping mapping = (GradeMapping)iter.next();
if(mapping.getId().equals(selectedGradeMappingId)) {
if(localGradebook.getGrade_type() == GradebookService.GRADE_TYPE_LETTER && mapping.getGradingScale().getUid().equals("LetterGradeMapping"))
{
isValidWithLetterGrade = false;
return;
}
localGradebook.setSelectedGradeMapping(mapping);
initGradeRows();
}
}
}
/**
* Action listener to reset the currently selected grade mapping to its default values.
* Other, not currently visible, changed unsaved grade mapping settings are left as they
* are.
*/
public void resetMappingValues(ActionEvent event) {
localGradebook.getSelectedGradeMapping().setDefaultValues();
}
/**
* Updates the gradebook to reflect the currently selected grade type and mapping.
*/
public String save() {
if (!isMappingValid(localGradebook.getSelectedGradeMapping())) {
return null;
}
if(!isConflictWithLetterGrade(localGradebook.getSelectedGradeMapping()))
{
isValidWithLetterGrade = false;
return null;
}
else
isValidWithLetterGrade = true;
try {
getGradebookManager().updateGradebook(localGradebook);
FacesUtil.addRedirectSafeMessage(getLocalizedString("feedback_options_submit_success"));
} catch (IllegalStateException ise) {
FacesUtil.addErrorMessage(getLocalizedString("feedback_options_illegal_change", new String[] {localGradebook.getSelectedGradeMapping().getName()}));
return null;
} catch (StaleObjectModificationException e) {
log.error(e);
FacesUtil.addErrorMessage(getLocalizedString("feedback_options_locking_failure"));
return null;
}
return "overview";
}
private boolean isMappingValid(GradeMapping gradeMapping) {
boolean valid = true;
Double previousPercentage = null;
for (Iterator iter = gradeMapping.getGrades().iterator(); iter.hasNext(); ) {
String grade = (String)iter.next();
Double percentage = (Double)gradeMapping.getValue(grade);
if (log.isDebugEnabled()) log.debug("checking percentage " + percentage + " for validity");
// Grades that are percentage-based need to remain percentage-based,
// be in descending order, and end with 0.
// Manual-only grades (which aren't associated with a percentage) always
// follow the lowest percentage-based grade, and must stay manual-only.
boolean manualOnly = (gradeMapping.getDefaultBottomPercents().get(grade) == null);
if (manualOnly) {
if (percentage != null) {
// This shouldn't happen, given the UI.
if (log.isErrorEnabled()) log.error("User " + getUserUid() + " attempted to set manual-only grade '" + grade + "' to be worth " + percentage + " percent");
percentage = null;
valid = false;
}
} else {
if (percentage == null) {
FacesUtil.addUniqueErrorMessage(getLocalizedString("feedback_options_require_all_values"));
valid = false;
} else if (percentage.doubleValue() < 0) {
FacesUtil.addUniqueErrorMessage(getLocalizedString("feedback_options_require_positive"));
valid = false;
} else if ((previousPercentage != null) && (previousPercentage.doubleValue() < percentage.doubleValue())) {
FacesUtil.addUniqueErrorMessage(getLocalizedString("feedback_options_require_descending_order"));
valid = false;
}
}
previousPercentage = percentage;
}
return valid;
}
private boolean isConflictWithLetterGrade(GradeMapping gradeMapping) {
boolean valid = true;
if(localGradebook.getGrade_type() != GradebookService.GRADE_TYPE_LETTER)
{
return valid;
}
if((gradeMapping.getGradingScale() != null && (!gradeMapping.getGradingScale().getUid().equals("LetterGradeMapping") && !gradeMapping.getGradingScale().getUid().equals("LetterGradePlusMinusMapping")))
|| (gradeMapping.getGradingScale() == null && (!gradeMapping.getName().equals("Letter Grades") && !gradeMapping.getName().equals("Letter Grades with +/-"))))
{
return valid;
}
Map defaultMapping = gradeMapping.getDefaultBottomPercents();
for (Iterator iter = gradeMapping.getGrades().iterator(); iter.hasNext(); )
{
String grade = (String)iter.next();
Double percentage = (Double)gradeMapping.getValue(grade);
Double defautPercentage = (Double)defaultMapping.get(grade);
if (log.isDebugEnabled()) log.debug("checking if percentage is being changed for letter grade type gradebook: " + percentage + " for validity");
if (percentage == null)
{
FacesUtil.addUniqueErrorMessage(getLocalizedString("feedback_options_require_all_values"));
valid = false;
}
else
{
if(!percentage.equals(defautPercentage))
{
valid = false;
}
}
}
return valid;
}
public String cancel() {
// Just in case we change the navigation to stay on this page,
// clear the work-in-progress indicator so that the user can
// start fresh.
workInProgress = false;
isValidWithLetterGrade = true;
return "overview";
}
public List getGradeMappingsSelectItems() {
return gradeMappingsSelectItems;
}
public void setGradeMappingsSelectItems(List gradeMappingsSelectItems) {
this.gradeMappingsSelectItems = gradeMappingsSelectItems;
}
public Long getSelectedGradeMappingId() {
return selectedGradeMappingId;
}
public void setSelectedGradeMappingId(Long selectedGradeMappingId) {
this.selectedGradeMappingId = selectedGradeMappingId;
}
public boolean getIsValidWithLetterGrade()
{
return isValidWithLetterGrade;
}
public void setIsValidWithLetterGrade(boolean isValidWithLetterGrade)
{
this.isValidWithLetterGrade = isValidWithLetterGrade;
}
}