/********************************************************************************** * * $Id: AssignmentBean.java $ * *********************************************************************************** * * Copyright (c) 2005, 2006, 2007, 2008, 2009 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.math.BigDecimal; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.HashMap; import javax.faces.application.FacesMessage; import javax.faces.event.ValueChangeEvent; import javax.faces.model.SelectItem; import javax.faces.validator.ValidatorException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.section.api.coursemanagement.EnrollmentRecord; import org.sakaiproject.section.api.facade.Role; import org.sakaiproject.service.gradebook.shared.ConflictingAssignmentNameException; import org.sakaiproject.service.gradebook.shared.StaleObjectModificationException; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.tool.gradebook.Assignment; import org.sakaiproject.tool.gradebook.Category; import org.sakaiproject.tool.gradebook.Gradebook; import org.sakaiproject.tool.gradebook.jsf.FacesUtil; import org.sakaiproject.util.ResourceLoader; import org.sakaiproject.service.gradebook.shared.MultipleAssignmentSavingException; import org.sakaiproject.service.gradebook.shared.GradebookService; public class AssignmentBean extends GradebookDependentBean implements Serializable { private static final Log logger = LogFactory.getLog(AssignmentBean.class); private Long assignmentId; private Assignment assignment; private List categoriesSelectList; private String assignmentCategory; // these 2 used to determine whether to zero-out the point value in applyPointsPossibleForDropScoreCategories private boolean categoryChanged; public boolean gradeEntryTypeChanged; private Category selectedCategory; private boolean selectedCategoryDropsScores; private boolean selectedAssignmentIsOnlyOne; private List<Category> categories; // added to support bulk gradebook item creation public List newBulkItems; public int numTotalItems = 1; public List addItemSelectList; public String gradeEntryType; public static final String UNASSIGNED_CATEGORY = "unassigned"; public static final String GB_ADJUSTMENT_ENTRY = "Adjustment"; private static final String GB_EDIT_ASSIGNMENT_PAGE = "editAssignment"; /** * To add the proper number of blank gradebook item objects for bulk creation */ private static final int NUM_EXTRA_ASSIGNMENT_ENTRIES = 50; protected void init() { if (logger.isDebugEnabled()) logger.debug("init assignment=" + assignment); if (assignment == null) { if (assignmentId != null) { assignment = getGradebookManager().getAssignment(assignmentId); } if (assignment == null) { // it is a new assignment assignment = new Assignment(); assignment.setReleased(true); } } // initialization; shouldn't enter here after category drop down changes if(selectedCategory == null && !getLocalizedString("cat_unassigned").equalsIgnoreCase(assignmentCategory)) { Category assignCategory = assignment.getCategory(); if (assignCategory != null) { assignmentCategory = assignCategory.getId().toString(); selectedCategoryDropsScores = assignCategory.isDropScores(); assignCategory.setAssignmentList(retrieveCategoryAssignmentList(assignCategory)); selectedAssignmentIsOnlyOne = isAssignmentTheOnlyOne(assignment, assignCategory); selectedCategory = assignCategory; } else { assignmentCategory = getLocalizedString("cat_unassigned"); } } categoriesSelectList = new ArrayList(); // The first choice is always "Unassigned" categoriesSelectList.add(new SelectItem(UNASSIGNED_CATEGORY, FacesUtil.getLocalizedString("cat_unassigned"))); List gbCategories = getGradebookManager().getCategories(getGradebookId()); if (gbCategories != null && gbCategories.size() > 0) { Iterator catIter = gbCategories.iterator(); while (catIter.hasNext()) { Category cat = (Category) catIter.next(); categoriesSelectList.add(new SelectItem(cat.getId().toString(), cat.getName())); } } // To support bulk creation of assignments if (newBulkItems == null) { newBulkItems = new ArrayList(); } // initialize the number of items to add dropdown addItemSelectList = new ArrayList(); addItemSelectList.add(new SelectItem("0", "")); for (int i = 1; i < NUM_EXTRA_ASSIGNMENT_ENTRIES; i++) { addItemSelectList.add(new SelectItem(new Integer(i).toString(), new Integer(i).toString())); } if(newBulkItems.size() == NUM_EXTRA_ASSIGNMENT_ENTRIES) { applyPointsPossibleForDropScoreCategories(newBulkItems); } else { for (int i = newBulkItems.size(); i < NUM_EXTRA_ASSIGNMENT_ENTRIES; i++) { BulkAssignmentDecoratedBean item = getNewAssignment(); if (i == 0) { item.setSaveThisItem("true"); } newBulkItems.add(item); } } } private boolean isAssignmentTheOnlyOne(Assignment assignment, Category category){ if(assignment != null && category != null && category.getAssignmentList() != null){ if(category.getAssignmentList().size() == 1) return ((Assignment)category.getAssignmentList().get(0)).getId().equals(assignment.getId()); else if(category.getAssignmentList().size() == 0) return true; else return false; }else{ return false; } } /* * sets pointsPossible for items in categories that drop scores * and sets the assignment's category, so that the ui can read item.assignment.category.dropScores */ private void applyPointsPossibleForDropScoreCategories(List items) { categories = getCategories(); Map<String, Category> categoryCache = new HashMap<String, Category>(); for(int i=0; i<items.size(); i++) { BulkAssignmentDecoratedBean bulkAssignment = (BulkAssignmentDecoratedBean)items.get(i); String assignmentCategory = bulkAssignment.getCategory(); if(getLocalizedString("cat_unassigned").equalsIgnoreCase(assignmentCategory)) { Category unassigned = new Category(); bulkAssignment.getAssignment().setCategory(unassigned); // set this unassigned category, so that in the ui, item.assignment.category.dropScores will return false if(categoryChanged || gradeEntryTypeChanged) { //bulkAssignment.setPointsPossible(null); bulkAssignment.getAssignment().setPointsPossible(null); } } else { for(int j=0; j<categories.size(); j++) { Category category = (Category)categories.get(j); if(assignmentCategory.equals(category.getId().toString())) { if(categoryCache.containsKey(category.getId().toString())){ category = categoryCache.get(category.getId().toString()); }else{ category = retrieveSelectedCategory(category.getId().toString(), true); categoryCache.put(category.getId().toString(), category); } bulkAssignment.getAssignment().setCategory(category); // set here, because need to read item.assignment.category.dropScores in the ui if(category.getAssignmentList() != null && category.getAssignmentList().size() > 0 && category.isDropScores() && !GB_ADJUSTMENT_ENTRY.equals(bulkAssignment.getSelectedGradeEntryValue())) { bulkAssignment.setPointsPossible(category.getItemValue().toString()); bulkAssignment.getAssignment().setPointsPossible(category.getItemValue()); } else if(categoryChanged || gradeEntryTypeChanged) { if (category.isDropScores()) { bulkAssignment.setPointsPossible(null); } bulkAssignment.getAssignment().setPointsPossible(null); } continue; } } } } } private Category getCategoryById(String categoryId) { if(categoryId == null) { return null; } Category category = null; for(int i=0; i<categories.size(); i++) { category = (Category)categories.get(i); String id = category.getId().toString(); if(id != null && categoryId.trim().equals(id)) { break; } } return category; } private BulkAssignmentDecoratedBean getNewAssignment() { Assignment assignment = new Assignment(); assignment.setReleased(true); if(selectedCategory != null && selectedCategory.isDropScores()) { assignment.setPointsPossible(selectedCategory.getItemValue()); } BulkAssignmentDecoratedBean bulkAssignmentDecoBean = new BulkAssignmentDecoratedBean(assignment, getItemCategoryString(assignment)); return bulkAssignmentDecoBean; } private String getItemCategoryString(Assignment assignment) { String assignmentCategory; Category assignCategory = assignment.getCategory(); if (assignCategory != null) { assignmentCategory = assignCategory.getId().toString(); } else { assignmentCategory = getLocalizedString("cat_unassigned"); } return assignmentCategory; } /** * Used to check if all gradebook items are valid before saving. Due to the way JSF works, had to turn off * validators for bulk assignments so had to perform checks here. * This is an all-or-nothing save, ie, either all items are OK and we save them all, or return to add page * and highlight errors. */ public String saveNewAssignment() { String resultString = "overview"; boolean saveAll = true; // keep list of new assignment names just in case // duplicates entered on screen List newAssignmentNameList = new ArrayList(); // used to hold assignments that are OK since we // need to determine if all are correct before saving List itemsToSave = new ArrayList(); Iterator assignIter = newBulkItems.iterator(); int i = 0; Map<String, Category> categoryCache = new HashMap<String, Category>(); while (i < numTotalItems && assignIter.hasNext()) { BulkAssignmentDecoratedBean bulkAssignDecoBean = (BulkAssignmentDecoratedBean) assignIter.next(); if (bulkAssignDecoBean.getBlnSaveThisItem()) { Assignment bulkAssignment = bulkAssignDecoBean.getAssignment(); // Check for blank entry else check if duplicate within items to be // added or with item currently in gradebook. if ("".equals(bulkAssignment.getName().toString().trim())) { bulkAssignDecoBean.setBulkNoTitleError("blank"); saveAll = false; resultString = "failure"; } else if (newAssignmentNameList.contains(bulkAssignment.getName().trim()) || ! getGradebookManager().checkValidName(getGradebookId(), bulkAssignment)){ bulkAssignDecoBean.setBulkNoTitleError("dup"); saveAll = false; resultString = "failure"; } else { bulkAssignDecoBean.setBulkNoTitleError("OK"); newAssignmentNameList.add(bulkAssignment.getName().trim()); } Category selectedCategory; if(categoryCache.containsKey(bulkAssignDecoBean.getCategory())){ selectedCategory = categoryCache.get(bulkAssignDecoBean.getCategory()); }else{ selectedCategory = retrieveSelectedCategory(bulkAssignDecoBean.getCategory(), true); categoryCache.put(bulkAssignDecoBean.getCategory(), selectedCategory); } boolean categoryDropsScores = false; if(selectedCategory != null && selectedCategory.isDropScores() && selectedCategory.getAssignmentList() != null && selectedCategory.getAssignmentList().size() > 0 ) { categoryDropsScores = true; if(!GB_ADJUSTMENT_ENTRY.equals(bulkAssignDecoBean.getSelectedGradeEntryValue())) { bulkAssignDecoBean.setPointsPossible(selectedCategory.getItemValue().toString()); // if category drops scores and is not adjustment, point value will come from the category level } } // Check if points possible is blank else convert to double. Exception at else point // means non-numeric value entered. if (bulkAssignDecoBean.getPointsPossible() == null || ("".equals(bulkAssignDecoBean.getPointsPossible().trim()))) { bulkAssignDecoBean.setBulkNoPointsError("blank"); saveAll = false; resultString = "failure"; } else { try { // Check if PointsPossible uses local number format String strPointsPossible = bulkAssignDecoBean.getPointsPossible(); NumberFormat nf = NumberFormat.getInstance(new ResourceLoader().getLocale()); double dblPointsPossible = new Double (nf.parse(strPointsPossible).doubleValue()); // Added per SAK-13459: did not validate if point value was valid (> zero) if (dblPointsPossible > 0) { // No more than 2 decimal places can be entered. BigDecimal bd = new BigDecimal(dblPointsPossible); bd = bd.setScale(2, BigDecimal.ROUND_HALF_UP); // Two decimal places double roundedVal = bd.doubleValue(); double diff = dblPointsPossible - roundedVal; if(diff != 0) { saveAll = false; resultString = "failure"; bulkAssignDecoBean.setBulkNoPointsError("precision"); } else { bulkAssignDecoBean.setBulkNoPointsError("OK"); bulkAssignDecoBean.getAssignment().setPointsPossible(new Double(bulkAssignDecoBean.getPointsPossible())); } } else { saveAll = false; resultString = "failure"; bulkAssignDecoBean.setBulkNoPointsError("invalid"); } } catch (Exception e) { bulkAssignDecoBean.setBulkNoPointsError("NaN"); saveAll = false; resultString = "failure"; } } if (saveAll) { bulkAssignDecoBean.getAssignment().setCategory(retrieveSelectedCategory(bulkAssignDecoBean.getCategory(), false)); // if points possible is still 0 at this point, set it to null to avoid Division By Zero exceptions. These should never be allowed in the database. if (null != bulkAssignDecoBean.getAssignment().getPointsPossible() && bulkAssignDecoBean.getAssignment().getPointsPossible()==0) bulkAssignDecoBean.getAssignment().setPointsPossible(null); itemsToSave.add(bulkAssignDecoBean.getAssignment()); } // Even if errors increment since we need to go back to add page i++; } } // Now ready to save, the only problem is due to duplicate names. if (saveAll) { try { getGradebookManager().createAssignments(getGradebookId(), itemsToSave); String authzLevel = (getGradebookBean().getAuthzService().isUserAbleToGradeAll(getGradebookUid())) ?"instructor" : "TA"; for (Iterator gbItemIter = itemsToSave.iterator(); gbItemIter.hasNext();) { String itemName = ((Assignment) gbItemIter.next()).getName(); FacesUtil.addRedirectSafeMessage(getLocalizedString("add_assignment_save", new String[] {itemName})); getGradebookBean().getEventTrackingService().postEvent("gradebook.newItem","/gradebook/"+getGradebookId()+"/"+itemName+"/"+authzLevel); } } catch (MultipleAssignmentSavingException e) { FacesUtil.addErrorMessage(FacesUtil.getLocalizedString("validation_messages_present")); resultString = "failure"; } } else { // There are errors so need to put an error message at top FacesUtil.addErrorMessage(FacesUtil.getLocalizedString("validation_messages_present")); } return resultString; } public String updateAssignment() { try { Category category = retrieveSelectedCategory(); assignment.setCategory(category); if(!GB_ADJUSTMENT_ENTRY.equals(assignment.getSelectedGradeEntryValue()) && category != null && category.isDropScores() && !isAssignmentTheOnlyOne(assignment, category)) { assignment.setPointsPossible(category.getItemValue()); // if category drops scores, point value will come from the category level } Assignment originalAssignment = getGradebookManager().getAssignment(assignmentId); Double origPointsPossible = originalAssignment.getPointsPossible(); Double newPointsPossible = assignment.getPointsPossible(); boolean scoresEnteredForAssignment = getGradebookManager().isEnteredAssignmentScores(assignmentId); /* If grade entry by percentage or letter and the points possible has changed for this assignment, * we need to convert all of the stored point values to retain the same value */ if ((getGradeEntryByPercent() || getGradeEntryByLetter()) && scoresEnteredForAssignment) { if (!newPointsPossible.equals(origPointsPossible)) { List enrollments = getSectionAwareness().getSiteMembersInRole(getGradebookUid(), Role.STUDENT); List studentUids = new ArrayList(); for(Iterator iter = enrollments.iterator(); iter.hasNext();) { studentUids.add(((EnrollmentRecord)iter.next()).getUser().getUserUid()); } getGradebookManager().convertGradePointsForUpdatedTotalPoints(getGradebook(), originalAssignment, assignment.getPointsPossible(), studentUids); } } getGradebookManager().updateAssignment(assignment); long dueDateMillis = -1; Date dueDate = assignment.getDueDate(); if (dueDate != null) dueDateMillis = dueDate.getTime(); getGradebookBean().getEventTrackingService().postEvent("gradebook.updateAssignment","/gradebook/"+getGradebookUid()+"/"+assignment.getName()+"/"+assignment.getPointsPossible()+"/"+dueDateMillis+"/"+assignment.isReleased()+"/"+assignment.isCounted()+"/"+getAuthzLevel()); if ((!origPointsPossible.equals(newPointsPossible)) && scoresEnteredForAssignment) { if (getGradeEntryByPercent()) FacesUtil.addRedirectSafeMessage(getLocalizedString("edit_assignment_save_percentage", new String[] {assignment.getName()})); else if (getGradeEntryByLetter()) FacesUtil.addRedirectSafeMessage(getLocalizedString("edit_assignment_save_converted", new String[] {assignment.getName()})); else FacesUtil.addRedirectSafeMessage(getLocalizedString("edit_assignment_save_scored", new String[] {assignment.getName()})); } else { FacesUtil.addRedirectSafeMessage(getLocalizedString("edit_assignment_save", new String[] {assignment.getName()})); } } catch (ConflictingAssignmentNameException e) { logger.error(e); FacesUtil.addErrorMessage(getLocalizedString("edit_assignment_name_conflict_failure")); return "failure"; } catch (StaleObjectModificationException e) { logger.error(e); FacesUtil.addErrorMessage(getLocalizedString("edit_assignment_locking_failure")); return "failure"; } return navigateBack(); } public String navigateToAssignmentDetails() { return navigateBack(); } /** * Go to assignment details page. InstructorViewBean contains duplicate * of this method, cannot migrate up to GradebookDependentBean since * needs assignmentId, which is defined here. */ public String navigateBack() { String breadcrumbPage = getBreadcrumbPage(); final Boolean middle = new Boolean((String) SessionManager.getCurrentToolSession().getAttribute("middle")); if (breadcrumbPage == null || middle) { AssignmentDetailsBean assignmentDetailsBean = (AssignmentDetailsBean)FacesUtil.resolveVariable("assignmentDetailsBean"); assignmentDetailsBean.setAssignmentId(assignmentId); assignmentDetailsBean.setBreadcrumbPage(breadcrumbPage); breadcrumbPage = "assignmentDetails"; } // wherever we go, we're not editing, and middle section // does not need to be shown. setNav(null, "false", null, "false", null); return breadcrumbPage; } /** * View maintenance methods. */ public Long getAssignmentId() { if (logger.isDebugEnabled()) logger.debug("getAssignmentId " + assignmentId); return assignmentId; } public void setAssignmentId(Long assignmentId) { if (logger.isDebugEnabled()) logger.debug("setAssignmentId " + assignmentId); if (assignmentId != null) { this.assignmentId = assignmentId; } } public Assignment getAssignment() { if (logger.isDebugEnabled()) logger.debug("getAssignment " + assignment); if (assignment == null) { if (assignmentId != null) { assignment = getGradebookManager().getAssignment(assignmentId); } if (assignment == null) { // it is a new assignment assignment = new Assignment(); assignment.setReleased(true); } } return assignment; } public List getCategoriesSelectList() { return categoriesSelectList; } public List getAddItemSelectList() { return addItemSelectList; } public String getAssignmentCategory() { return assignmentCategory; } public void setAssignmentCategory(String assignmentCategory) { this.assignmentCategory = assignmentCategory; } /** * getNewBulkItems * * Generates and returns a List of blank Assignment objects. * Used to support bulk gradebook item creation. */ public List getNewBulkItems() { if (newBulkItems == null) { newBulkItems = new ArrayList(); } for (int i = newBulkItems.size(); i < NUM_EXTRA_ASSIGNMENT_ENTRIES; i++) { newBulkItems.add(getNewAssignment()); } return newBulkItems; } public void setNewBulkItems(List newBulkItems) { this.newBulkItems = newBulkItems; } public List getCategories() { if(categories == null) { categories = getGradebookManager().getCategories(getGradebookId()); } return categories; } public int getNumTotalItems() { return numTotalItems; } public void setNumTotalItems(int numTotalItems) { this.numTotalItems = numTotalItems; } public Gradebook getLocalGradebook() { return getGradebook(); } /** * Returns the Category associated with assignmentCategory * If unassigned or not found, returns null * * added parameterized version to support bulk gradebook item creation */ private Category retrieveSelectedCategory() { return retrieveSelectedCategory(assignmentCategory, true); } private Category retrieveSelectedCategory(String assignmentCategory, boolean includeAssignments) { Long catId = null; Category category = null; if (assignmentCategory != null && !assignmentCategory.equals(UNASSIGNED_CATEGORY)) { try { catId = new Long(assignmentCategory); } catch (Exception e) { catId = null; } if (catId != null) { // check to make sure there is a corresponding category category = getGradebookManager().getCategory(catId); if(includeAssignments){ //populate assignments list category.setAssignmentList(retrieveCategoryAssignmentList(category)); } } } return category; } private List retrieveCategoryAssignmentList(Category cat){ List assignmentsToUpdate = new ArrayList(); if(cat != null){ List assignments = cat.getAssignmentList(); if(cat.isDropScores() && (assignments == null || assignments.size() == 0)) { // don't populate, if assignments are already in category (to improve performance) assignments = getGradebookManager().getAssignmentsForCategory(cat.getId()); // only include assignments which are not adjustments must not update adjustment item pointsPossible for(Object o : assignments) { if(o instanceof Assignment) { Assignment assignment = (Assignment)o; if(!Assignment.item_type_adjustment.equals(assignment.getItemType())) { assignmentsToUpdate.add(assignment); } } } } } return assignmentsToUpdate; } public boolean isSelectedCategoryDropsScores() { return selectedCategoryDropsScores; } public void setSelectedCategoryDropsScores(boolean selectedCategoryDropsScores) { this.selectedCategoryDropsScores = selectedCategoryDropsScores; } public Category getSelectedCategory() { return selectedCategory; } public void setSelectedCategory(Category selectedCategory) { this.selectedCategory = selectedCategory; } public String processCategoryChangeInEditAssignment(ValueChangeEvent vce) { String changeCategory = (String) vce.getNewValue(); assignmentCategory = changeCategory; if(vce.getOldValue() != null && vce.getNewValue() != null && !vce.getOldValue().equals(vce.getNewValue())) { if(changeCategory.equals(UNASSIGNED_CATEGORY)) { selectedCategoryDropsScores = false; selectedAssignmentIsOnlyOne = false; selectedCategory = null; assignmentCategory = getLocalizedString("cat_unassigned"); } else { List<Category> categories = getGradebookManager().getCategories(getGradebookId()); if (categories != null && categories.size() > 0) { for (Category category : categories) { if(changeCategory.equals(category.getId().toString())) { selectedCategoryDropsScores = category.isDropScores(); category.setAssignmentList(retrieveCategoryAssignmentList(category)); selectedCategory = category; selectedAssignmentIsOnlyOne = isAssignmentTheOnlyOne(assignment, selectedCategory); assignmentCategory = category.getId().toString(); break; } } } } } return GB_EDIT_ASSIGNMENT_PAGE; } /** * For bulk assignments, need to set the proper classes as a string */ public String getRowClasses() { StringBuffer rowClasses = new StringBuffer(); if (newBulkItems == null) { newBulkItems = getNewBulkItems(); } //if shown in UI, set class to 'bogus show' otherwise 'bogus hide' for (int i=0; i< newBulkItems.size(); i++){ Object obj = newBulkItems.get(i); if(obj instanceof BulkAssignmentDecoratedBean){ BulkAssignmentDecoratedBean assignment = (BulkAssignmentDecoratedBean) newBulkItems.get(i); if (i != 0) rowClasses.append(","); if (assignment.getBlnSaveThisItem() || i == 0) { rowClasses.append("show bogus"); } else { rowClasses.append("hide bogus"); } } } return rowClasses.toString(); } public boolean isSelectedAssignmentIsOnlyOne() { return selectedAssignmentIsOnlyOne; } public void setSelectedAssignmentIsOnlyOne(boolean selectedAssignmentIsOnlyOne) { this.selectedAssignmentIsOnlyOne = selectedAssignmentIsOnlyOne; } }