/**********************************************************************************
*
* $Id: GradebookSetupBean.java 20001 2007-04-01 19:41:33Z wagnermr@iupui.edu $
*
***********************************************************************************
*
* 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.osedu.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.List;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
import java.util.Set;
import javax.faces.event.ActionEvent;
import javax.faces.event.ValueChangeEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.tool.gradebook.Assignment;
import org.sakaiproject.tool.gradebook.Category;
import org.sakaiproject.tool.gradebook.GradeMapping;
import org.sakaiproject.tool.gradebook.Gradebook;
import org.sakaiproject.tool.gradebook.LetterGradePercentMapping;
import org.sakaiproject.tool.gradebook.Permission;
import org.sakaiproject.tool.gradebook.jsf.FacesUtil;
import org.sakaiproject.service.gradebook.shared.ConflictingCategoryNameException;
import org.sakaiproject.service.gradebook.shared.GradebookService;
import org.sakaiproject.service.gradebook.shared.StaleObjectModificationException;
import org.sakaiproject.component.cover.ServerConfigurationService;
public class GradebookSetupBean extends GradebookDependentBean implements Serializable
{
private static final Log logger = LogFactory.getLog(GradebookSetupBean.class);
private String gradeEntryMethod;
private String categorySetting;
private List categories;
private Gradebook localGradebook;
private List categoriesToRemove;
private double runningTotal;
private double neededTotal;
private String pageName;
private List letterGradeRows;
private List letterGradesList;
private LetterGradePercentMapping lgpm;
private LetterGradePercentMapping defaultLGPM;
private boolean enableLetterGrade = false;
private boolean isValidWithCourseGrade = true;
private static final int NUM_EXTRA_CAT_ENTRIES = 50;
private static final String ENTRY_OPT_POINTS = "points";
private static final String ENTRY_OPT_PERCENT = "percent";
private static final String ENTRY_OPT_LETTER = "letterGrade";
private static final String CATEGORY_OPT_NONE = "noCategories";
private static final String CATEGORY_OPT_CAT_ONLY = "onlyCategories";
private static final String CATEGORY_OPT_CAT_AND_WEIGHT = "categoriesAndWeighting";
private static final String GB_SETUP_PAGE = "gradebookSetup";
private static final String GB_OVERVIEW_PAGE = "overview";
private static final String ROW_INDEX_PARAM = "rowIndex";
protected void init()
{
if (localGradebook == null)
{
localGradebook = getGradebook();
categories = getGradebookManager().getCategoriesWithStats(getGradebookId(),Assignment.DEFAULT_SORT, true, Category.SORT_BY_NAME, true);
convertWeightsFromDecimalsToPercentages();
intializeGradeEntryAndCategorySettings();
categoriesToRemove = new ArrayList();
}
calculateRunningTotal();
defaultLGPM = getGradebookManager().getDefaultLetterGradePercentMapping();
letterGradesList = new ArrayList(defaultLGPM.getGradeMap().keySet());
Collections.sort(letterGradesList, GradebookService.lettergradeComparator);
lgpm = getGradebookManager().getLetterGradePercentMapping(localGradebook);
if (lgpm != null && lgpm.getGradeMap().size() > 0) {
initLetterGradeRows();
}
}
private void initLetterGradeRows() {
letterGradeRows = new ArrayList();
for (Iterator iter = letterGradesList.iterator(); iter.hasNext(); ) {
String grade = (String)iter.next();
// Bottom grades (with a lower bound of 0%)
Double d = defaultLGPM.getValue(grade);
boolean editable = ((d != null) && (d.doubleValue() > 0.0));
letterGradeRows.add(new LetterGradeRow(lgpm, grade, editable));
}
}
private void reset()
{
localGradebook = null;
categories = null;
categorySetting = null;
gradeEntryMethod = null;
isValidWithCourseGrade = true;
}
public Gradebook getLocalGradebook()
{
return localGradebook;
}
public String getGradeEntryMethod()
{
return gradeEntryMethod;
}
public void setGradeEntryMethod(String gradeEntryMethod)
{
this.gradeEntryMethod = gradeEntryMethod;
}
public String getCategorySetting()
{
return categorySetting;
}
public void setCategorySetting(String categorySetting)
{
this.categorySetting = categorySetting;
}
/**
*
* @return String value of display:none or display:block for initial display
* of grade entry scale
*/
public String getDisplayGradeEntryScaleStyle()
{
if (gradeEntryMethod != null && gradeEntryMethod.equals(ENTRY_OPT_LETTER))
return "display:block;";
else
return "display:none;";
}
/**
* Returns true if categories are used in gb
* @return
*/
public boolean isDisplayCategories()
{
return !categorySetting.equals(CATEGORY_OPT_NONE);
}
/**
* Returns true if weighting is used in gb
* @return
*/
public boolean isDisplayWeighting()
{
return categorySetting.equals(CATEGORY_OPT_CAT_AND_WEIGHT);
}
/**
* Save gradebook settings (including categories and weighting)
* @return
*/
public String processSaveGradebookSetup()
{
if (gradeEntryMethod == null || (!gradeEntryMethod.equals(ENTRY_OPT_POINTS) &&
!gradeEntryMethod.equals(ENTRY_OPT_PERCENT) && !gradeEntryMethod.equals(ENTRY_OPT_LETTER)))
{
FacesUtil.addErrorMessage(getLocalizedString("grade_entry_invalid"));
return "failure";
}
if(!isConflictWithCourseGrade())
{
isValidWithCourseGrade = false;
return null;
}
else
isValidWithCourseGrade = true;
if (gradeEntryMethod.equals(ENTRY_OPT_PERCENT))
{
localGradebook.setGrade_type(GradebookService.GRADE_TYPE_PERCENTAGE);
}
else if (gradeEntryMethod.equals(ENTRY_OPT_LETTER))
{
localGradebook.setGrade_type(GradebookService.GRADE_TYPE_LETTER);
}
else
{
localGradebook.setGrade_type(GradebookService.GRADE_TYPE_POINTS);
}
// SAK-10879 - comment out ability to customize lgpm
/*
if (lgpm != null) {
if (!isMappingValid(lgpm)) {
return "failure";
}
LetterGradePercentMapping originalLgpm = getGradebookManager().getLetterGradePercentMapping(localGradebook);
boolean lgpmUpdate = false;
for (Iterator iter = letterGradesList.iterator(); iter.hasNext(); ) {
String grade = (String) iter.next();
Double originalPercent = null;
if (originalLgpm != null)
originalPercent = (Double) originalLgpm.getValue(grade);
Double currentPercent = (Double) lgpm.getValue(grade);
if (!originalPercent.equals(currentPercent)) {
lgpmUpdate = true;
break;
}
}
if (lgpmUpdate) {
getGradebookManager().saveOrUpdateLetterGradePercentMapping(lgpm.getGradeMap(), localGradebook);
}
}*/
if (categorySetting == null || (!categorySetting.equals(CATEGORY_OPT_CAT_ONLY) &&
!categorySetting.equals(CATEGORY_OPT_CAT_AND_WEIGHT) && !categorySetting.equals(CATEGORY_OPT_NONE)))
{
FacesUtil.addErrorMessage(getLocalizedString("cat_setting_invalid"));
return "failure";
}
if (categorySetting.equals(CATEGORY_OPT_NONE))
{
localGradebook.setCategory_type(GradebookService.CATEGORY_TYPE_NO_CATEGORY);
// remove current categories
List gbCategories = getGradebookManager().getCategories(localGradebook.getId());
if (gbCategories != null && !gbCategories.isEmpty())
{
Iterator removeIter = gbCategories.iterator();
while (removeIter.hasNext())
{
Category removeCat = (Category) removeIter.next();
getGradebookManager().removeCategory(removeCat.getId());
}
}
// check to see if any permissions need to be removed
List sections = getAllSections();
List gbPermissions = getGradebookManager().getPermissionsForGB(localGradebook.getId());
if (gbPermissions != null) {
for (Iterator permIter = gbPermissions.iterator(); permIter.hasNext();) {
Permission perm = (Permission) permIter.next();
// if there is a specific category associated with this permission or if
// there are no sections defined in the site, we need to delete this permission
if (perm.getCategoryId() != null || sections == null || sections.size() == 0) {
logger.debug("Permission " + perm.getId() + " was deleted b/c gb changed to no categories");
getGradebookManager().deletePermission(perm);
}
}
}
getGradebookManager().updateGradebook(localGradebook);
reset();
FacesUtil.addRedirectSafeMessage(getLocalizedString("gb_save_msg"));
return null;
}
// if we are going from no categories to having categories, we need to set
// counted = false for all existing assignments b/c the category will
// now be "unassigned"
/*if (localGradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_NO_CATEGORY &&
(categorySetting.equals(CATEGORY_OPT_CAT_ONLY) || categorySetting.equals(CATEGORY_OPT_CAT_AND_WEIGHT))) {
List assignmentsInGb = getGradebookManager().getAssignments(getGradebookId(), Assignment.DEFAULT_SORT, true);
if (assignmentsInGb != null && !assignmentsInGb.isEmpty()) {
Iterator assignIter = assignmentsInGb.iterator();
while (assignIter.hasNext()) {
Assignment assignment = (Assignment) assignIter.next();
assignment.setCounted(false);
getGradebookManager().updateAssignment(assignment);
}
}
} */
if (categorySetting.equals(CATEGORY_OPT_CAT_ONLY))
{
localGradebook.setCategory_type(GradebookService.CATEGORY_TYPE_ONLY_CATEGORY);
// set all weighting to 0 for existing categories
Iterator unweightIter = categories.iterator();
while (unweightIter.hasNext())
{
Category unweightCat = (Category) unweightIter.next();
unweightCat.setWeight(new Double(0));
}
}
else if (categorySetting.equals(CATEGORY_OPT_CAT_AND_WEIGHT))
{
localGradebook.setCategory_type(GradebookService.CATEGORY_TYPE_WEIGHTED_CATEGORY);
// we need to make sure all of the weights add up to 100
calculateRunningTotal();
if (runningTotal != 100)
{
FacesUtil.addErrorMessage(getLocalizedString("cat_weight_total_not_100"));
return "failure";
}
}
/* now we need to iterate through the categories and
1) remove categories
2) add any new categories
3) update existing categories */
Iterator catIter = categories.iterator();
while (catIter.hasNext())
{
try {
Object obj = catIter.next();
if(!(obj instanceof Category)){
continue;
}
Category uiCategory = (Category) obj;
Long categoryId = uiCategory.getId();
String categoryName = uiCategory.getName();
if ((categoryName == null || categoryName.trim().length() < 1) && categoryId != null)
{
categoriesToRemove.add(categoryId);
}
if (categoryName != null && categoryName.length() > 0)
{
// treat blank weight fields as 0
if (localGradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_WEIGHTED_CATEGORY &&
uiCategory.getWeight() == null) {
uiCategory.setWeight(new Double(0));
}
if (categoryId == null) {
// must be a new or blank category
if (uiCategory.getWeight() != null && uiCategory.getWeight().doubleValue() > 0) {
getGradebookManager().createCategory(localGradebook.getId(), categoryName.trim(), new Double(uiCategory.getWeight().doubleValue()/100), 0);
} else {
getGradebookManager().createCategory(localGradebook.getId(), categoryName.trim(), uiCategory.getWeight(), 0);
}
}
else {
// we are updating an existing category
Category updatedCategory = getGradebookManager().getCategory(categoryId);
updatedCategory.setName(categoryName.trim());
if (uiCategory.getWeight() != null && uiCategory.getWeight().doubleValue() > 0) {
updatedCategory.setWeight(new Double (uiCategory.getWeight().doubleValue()/100));
} else {
updatedCategory.setWeight(uiCategory.getWeight());
}
getGradebookManager().updateCategory(updatedCategory);
}
}
}
catch (ConflictingCategoryNameException cne) {
FacesUtil.addErrorMessage(getLocalizedString("cat_same_name_error"));
return "failure";
}
catch (StaleObjectModificationException e) {
logger.error(e);
FacesUtil.addErrorMessage(getLocalizedString("cat_locking_failure"));
return "failure";
}
}
// remove any categories marked to remove
if (categoriesToRemove != null && categoriesToRemove.size() > 0) {
Iterator removeIter = categoriesToRemove.iterator();
while (removeIter.hasNext()) {
Long removeId = (Long) removeIter.next();
getGradebookManager().removeCategory(removeId);
}
List permsToRemove = getGradebookManager().getPermissionsForGBForCategoryIds(localGradebook.getId(), categoriesToRemove);
if (!permsToRemove.isEmpty()) {
for (Iterator permIter = permsToRemove.iterator(); permIter.hasNext();) {
Permission perm = (Permission) permIter.next();
logger.debug("Permission " + perm.getId() + " was deleted b/c category deleted");
getGradebookManager().deletePermission(perm);
}
}
}
getGradebookManager().updateGradebook(localGradebook);
FacesUtil.addRedirectSafeMessage(getLocalizedString("gb_save_msg"));
reset();
return null;
}
/**
* Removes the selected category from the local list. If category
* exists in gb, retains id for future use if/when setup is saved
* @param event
* @return
*/
public String processRemoveCategory(ActionEvent event)
{
if (categories == null || categories.isEmpty())
return GB_SETUP_PAGE;
try
{
Map params = FacesUtil.getEventParameterMap(event);
Integer index = (Integer) params.get(ROW_INDEX_PARAM);
if (index == null) {
return GB_SETUP_PAGE;
}
int indexToRemove = index.intValue();
Category catToRemove = (Category)categories.get(indexToRemove);
// new categories will not have an id yet so don't need to be retained
if (catToRemove.getId() != null)
{
categoriesToRemove.add(catToRemove.getId());
}
categories.remove(indexToRemove);
}
catch(Exception e)
{
// do nothing
}
return GB_SETUP_PAGE;
}
public String processCategorySettingChange(ValueChangeEvent vce)
{
String changeAssign = (String) vce.getNewValue();
if (changeAssign != null && (changeAssign.equals(CATEGORY_OPT_NONE) ||
changeAssign.equals(CATEGORY_OPT_CAT_AND_WEIGHT) ||
changeAssign.equals(CATEGORY_OPT_CAT_ONLY)))
{
categorySetting = changeAssign;
}
return GB_SETUP_PAGE;
}
public String processCancelGradebookSetup()
{
reset();
return GB_OVERVIEW_PAGE;
}
/*
* Returns list of categories
* Also includes blank categories to allow the user to enter new categories
*/
public List getCategories()
{
//first, iterate through the list and remove blank lines
for (int i=0; i < categories.size(); i++)
{
Object obj = categories.get(i);
if(!(obj instanceof Category)){
categories.remove(i);
continue;
}
Category cat = (Category)categories.get(i);
int assignmentCount = 0;
if(cat.getAssignmentList() != null){
assignmentCount = cat.getAssignmentList().size();
}
cat.setAssignmentCount(assignmentCount);
if (cat.getName() == null || cat.getName().trim().length() == 0)
{
if (cat.getId() != null)
{
// this will take care of instances where user just deleted cat name
// instead of hitting "remove"
categoriesToRemove.add(cat.getId());
}
categories.remove(cat);
i--;
}
}
// always display 5 blank entries for new categories
for (int i=0; i < NUM_EXTRA_CAT_ENTRIES; i++)
{
Category blankCat = new Category();
categories.add(blankCat);
}
return categories;
}
public String getRowClasses()
{
StringBuilder rowClasses = new StringBuilder();
//first add the row class "bogus" for current categories
for (int i=0; i<categories.size(); i++){
Object obj = categories.get(i);
if(!(obj instanceof Category)){
continue;
}
Category cat = (Category)categories.get(i);
if (cat.getName() != null && cat.getName().trim().length() != 0)
{
if(i != 0){
rowClasses.append(",");
}
rowClasses.append("bogus");
}
}
//add row class "bogus_hide" for blank categories
for (int i=0; i < NUM_EXTRA_CAT_ENTRIES; i++){
if(i == 0 && categories.size() == 0){
rowClasses.append("bogus");
}
rowClasses.append(",");
rowClasses.append("bogus hide");
}
return rowClasses.toString();
}
/**
* Returns sum of all category weights
* @return
*/
public double getRunningTotal()
{
return runningTotal;
}
/**
* Returns % needed to reach 100% for category weights
* @return
*/
public double getNeededTotal()
{
return neededTotal;
}
/**
* Simplifies some javascript/rendering relationships. The highlight
* class is only applied if the running total not equal to 100%
* @return
*/
public String getRunningTotalStyle()
{
if (runningTotal != 100)
return "highlight";
return "";
}
/**
* For retaining the pageName variable upon save or cancel
*/
public String getPageName() {
return pageName;
}
public void setPageName(String pageName) {
this.pageName = pageName;
}
/**
* Grading scale used if grade entry by letter
* @return
*/
public List getLetterGradeRows() {
return letterGradeRows;
}
public void setLetterGradeRows(List letterGradeRows) {
this.letterGradeRows = letterGradeRows;
}
/**
* Set gradeEntryType and categorySetting
*
*/
private void intializeGradeEntryAndCategorySettings()
{
// Grade entry setting
int gradeEntryType = localGradebook.getGrade_type();
if (gradeEntryType == GradebookService.GRADE_TYPE_PERCENTAGE)
gradeEntryMethod = ENTRY_OPT_PERCENT;
else if (gradeEntryType == GradebookService.GRADE_TYPE_LETTER)
gradeEntryMethod = ENTRY_OPT_LETTER;
else
gradeEntryMethod = ENTRY_OPT_POINTS;
// Category setting
int categoryType = localGradebook.getCategory_type();
if (categoryType == GradebookService.CATEGORY_TYPE_ONLY_CATEGORY)
categorySetting = CATEGORY_OPT_CAT_ONLY;
else if (categoryType == GradebookService.CATEGORY_TYPE_WEIGHTED_CATEGORY)
categorySetting = CATEGORY_OPT_CAT_AND_WEIGHT;
else
categorySetting = CATEGORY_OPT_NONE;
}
/**
* Calculates the sum of the category weights
* @return
*/
private void calculateRunningTotal()
{
double total = 0;
if (categories != null && categories.size() > 0)
{
Iterator catIter = categories.iterator();
while (catIter.hasNext())
{
Object obj = catIter.next();
if(!(obj instanceof Category)){
continue;
}
Category cat = (Category) obj;
if (cat.getWeight() != null)
{
double weight = cat.getWeight().doubleValue();
total += weight;
}
}
}
runningTotal = total;
neededTotal = 100 - total;
}
/**
* Because we display input as "percentage" to user but store it as
* decimal, we need a way to convert our weights from decimal to %
*/
private void convertWeightsFromDecimalsToPercentages() {
if (!getWeightingEnabled())
return;
if (categories != null && !categories.isEmpty()) {
Iterator iter = categories.iterator();
while (iter.hasNext()) {
Object obj = iter.next();
if(!(obj instanceof Category)){
continue;
}
Category myCat = (Category) obj;
Double weight = myCat.getWeight();
if (weight != null && weight.doubleValue() > 0) {
myCat.setWeight(new Double(weight.doubleValue() * 100));
}
}
}
}
private boolean isMappingValid(LetterGradePercentMapping lgpm) {
boolean valid = true;
Double previousPercentage = null;
for (Iterator iter = letterGradesList.iterator(); iter.hasNext(); ) {
String grade = (String)iter.next();
Double percentage = (Double)lgpm.getValue(grade);
if (logger.isDebugEnabled()) logger.debug("checking percentage " + percentage + " for validity");
// Grades that are percentage-based need to remain percentage-based,
// be in descending order, and end with 0.
if (percentage == null) {
FacesUtil.addUniqueErrorMessage(getLocalizedString("gb_setup_require_all_values"));
valid = false;
} else if (percentage.doubleValue() < 0) {
FacesUtil.addUniqueErrorMessage(getLocalizedString("gb_setup_require_positive"));
valid = false;
} else if ((previousPercentage != null) && (previousPercentage.doubleValue() < percentage.doubleValue())) {
FacesUtil.addUniqueErrorMessage(getLocalizedString("gb_setup_require_descending_order"));
valid = false;
}
previousPercentage = percentage;
}
return valid;
}
/**
* UI for the letter grade entry scale
*/
public class LetterGradeRow implements Serializable {
private String grade;
private boolean editable;
private LetterGradePercentMapping lgpm;
public LetterGradeRow() {
}
public LetterGradeRow(LetterGradePercentMapping lgpm, String grade, boolean editable) {
this.lgpm = lgpm;
this.grade = grade;
this.editable = editable;
}
public String getGrade() {
return grade;
}
public Double getMappingValue() {
return (Double)lgpm.getGradeMap().get(grade);
}
public void setMappingValue(Double value) {
lgpm.getGradeMap().put(grade, value);
}
public boolean isEditable() {
return editable;
}
}
public boolean getEnableLetterGrade()
{
enableLetterGrade = ServerConfigurationService.getBoolean(GradebookService.enableLetterGradeString, false);
return enableLetterGrade;
}
public void setEnableLetterGrade(boolean enableLetterGrade)
{
this.enableLetterGrade = enableLetterGrade;
}
public boolean getIsValidWithCourseGrade()
{
return isValidWithCourseGrade;
}
public void setIsValidWithCourseGrade(boolean isValidWithCourseGrade)
{
this.isValidWithCourseGrade = isValidWithCourseGrade;
}
public boolean isConflictWithCourseGrade()
{
Gradebook gb = getGradebookManager().getGradebookWithGradeMappings(getGradebookManager().getGradebook(localGradebook.getUid()).getId());
if (gradeEntryMethod.equals(ENTRY_OPT_LETTER))
{
if((gb.getSelectedGradeMapping().getGradingScale() != null && gb.getSelectedGradeMapping().getGradingScale().getUid().equals("LetterGradeMapping"))
|| (gb.getSelectedGradeMapping().getGradingScale() == null && gb.getSelectedGradeMapping().getName().equals("Letter Grades")))
{
return false;
}
Set mappings = gb.getGradeMappings();
for(Iterator iter = mappings.iterator(); iter.hasNext();)
{
GradeMapping gm = (GradeMapping) iter.next();
if(gm != null)
{
if((gm.getGradingScale() != null && (gm.getGradingScale().getUid().equals("LetterGradeMapping") || gm.getGradingScale().getUid().equals("LetterGradePlusMinusMapping")))
|| (gm.getGradingScale() == null && (gb.getSelectedGradeMapping().getName().equals("Letter Grades") || gb.getSelectedGradeMapping().getName().equals("Letter Grades with +/-"))))
{
Map defaultMapping = gm.getDefaultBottomPercents();
for (Iterator gradeIter = gm.getGrades().iterator(); gradeIter.hasNext(); )
{
String grade = (String)gradeIter.next();
Double percentage = (Double)gm.getValue(grade);
Double defautPercentage = (Double)defaultMapping.get(grade);
if (percentage != null && !percentage.equals(defautPercentage))
{
return false;
}
}
}
}
}
}
return true;
}
}