org.sakaiproject.component.gradebook.GradebookServiceHibernateImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.component.gradebook.GradebookServiceHibernateImpl.java

Source

/**********************************************************************************
*
* $Id$
*
***********************************************************************************
*
 * 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.component.gradebook;

import java.math.BigDecimal;
import java.sql.SQLException;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.HashSet;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.StaleObjectStateException;
import org.sakaiproject.service.gradebook.shared.AssessmentNotFoundException;
import org.sakaiproject.service.gradebook.shared.AssignmentHasIllegalPointsException;
import org.sakaiproject.service.gradebook.shared.CategoryDefinition;
import org.sakaiproject.service.gradebook.shared.CommentDefinition;
import org.sakaiproject.service.gradebook.shared.GradeDefinition;
import org.sakaiproject.service.gradebook.shared.GradeMappingDefinition;
import org.sakaiproject.service.gradebook.shared.ConflictingAssignmentNameException;
import org.sakaiproject.service.gradebook.shared.ConflictingExternalIdException;
import org.sakaiproject.service.gradebook.shared.GradebookExternalAssessmentService;
import org.sakaiproject.service.gradebook.shared.GradebookFrameworkService;
import org.sakaiproject.service.gradebook.shared.GradebookInformation;
import org.sakaiproject.service.gradebook.shared.GradebookNotFoundException;
import org.sakaiproject.service.gradebook.shared.GradebookService;
import org.sakaiproject.service.gradebook.shared.GradebookPermissionService;
import org.sakaiproject.service.gradebook.shared.InvalidGradeException;
import org.sakaiproject.service.gradebook.shared.SortType;
import org.sakaiproject.service.gradebook.shared.StaleObjectModificationException;
import org.sakaiproject.tool.gradebook.Assignment;
import org.sakaiproject.tool.gradebook.AssignmentGradeRecord;
import org.sakaiproject.tool.gradebook.Comment;
import org.sakaiproject.tool.gradebook.GradableObject;
import org.sakaiproject.tool.gradebook.GradeMapping;
import org.sakaiproject.tool.gradebook.Gradebook;
import org.sakaiproject.tool.gradebook.GradingEvent;
import org.sakaiproject.tool.gradebook.LetterGradePercentMapping;
import org.sakaiproject.tool.gradebook.facades.Authz;
import org.sakaiproject.tool.gradebook.CourseGradeRecord;
import org.sakaiproject.tool.gradebook.CourseGrade;
import org.sakaiproject.tool.gradebook.Category;
import org.sakaiproject.tool.gradebook.facades.EventTrackingService;
import org.sakaiproject.util.ResourceLoader;
import org.sakaiproject.section.api.coursemanagement.EnrollmentRecord;
import org.sakaiproject.section.api.coursemanagement.CourseSection;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException;

/**
 * A Hibernate implementation of GradebookService.
 */
public class GradebookServiceHibernateImpl extends BaseHibernateManager implements GradebookService {
    private static final Log log = LogFactory.getLog(GradebookServiceHibernateImpl.class);

    private Authz authz;
    private GradebookPermissionService gradebookPermissionService;
    private EventTrackingService eventTrackingService;

    @Override
    public boolean isAssignmentDefined(final String gradebookUid, final String assignmentName)
            throws GradebookNotFoundException {
        if (!isUserAbleToViewAssignments(gradebookUid)) {
            log.warn("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                    + " attempted to check for assignment " + assignmentName);
            throw new SecurityException("You do not have permission to perform this operation");
        }
        @SuppressWarnings("unchecked")
        Assignment assignment = (Assignment) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                return getAssignmentWithoutStats(gradebookUid, assignmentName, session);
            }
        });
        return (assignment != null);
    }

    private boolean isUserAbleToViewAssignments(String gradebookUid) {
        Authz authz = getAuthz();
        return (authz.isUserAbleToEditAssessments(gradebookUid) || authz.isUserAbleToGrade(gradebookUid));
    }

    @Override
    public boolean isUserAbleToGradeItemForStudent(String gradebookUid, Long itemId, String studentUid) {
        return getAuthz().isUserAbleToGradeItemForStudent(gradebookUid, itemId, studentUid);
    }

    @Override
    public boolean isUserAbleToViewItemForStudent(String gradebookUid, Long itemId, String studentUid) {
        return getAuthz().isUserAbleToViewItemForStudent(gradebookUid, itemId, studentUid);
    }

    @Override
    public String getGradeViewFunctionForUserForStudentForItem(String gradebookUid, Long itemId,
            String studentUid) {
        return getAuthz().getGradeViewFunctionForUserForStudentForItem(gradebookUid, itemId, studentUid);
    }

    @Override
    public List<org.sakaiproject.service.gradebook.shared.Assignment> getAssignments(String gradebookUid)
            throws GradebookNotFoundException {
        return getAssignments(gradebookUid, SortType.SORT_BY_NONE);
    }

    @Override
    public List<org.sakaiproject.service.gradebook.shared.Assignment> getAssignments(String gradebookUid,
            SortType sortBy) throws GradebookNotFoundException {
        if (!isUserAbleToViewAssignments(gradebookUid)) {
            log.warn("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                    + " attempted to get assignments list");
            throw new SecurityException("You do not have permission to perform this operation");
        }

        final Long gradebookId = getGradebook(gradebookUid).getId();

        @SuppressWarnings({ "unchecked", "rawtypes" })
        List<Assignment> internalAssignments = (List<Assignment>) getHibernateTemplate()
                .execute(new HibernateCallback() {
                    public Object doInHibernate(Session session) throws HibernateException {
                        return getAssignments(gradebookId, session);
                    }
                });

        sortAssignments(internalAssignments, sortBy, true);

        List<org.sakaiproject.service.gradebook.shared.Assignment> assignments = new ArrayList<org.sakaiproject.service.gradebook.shared.Assignment>();
        for (Iterator<Assignment> iter = internalAssignments.iterator(); iter.hasNext();) {
            Assignment assignment = (Assignment) iter.next();
            assignments.add(getAssignmentDefinition(assignment));
        }
        return assignments;
    }

    @Override
    public org.sakaiproject.service.gradebook.shared.Assignment getAssignment(final String gradebookUid,
            final Long assignmentId) throws AssessmentNotFoundException {
        if (assignmentId == null || gradebookUid == null) {
            throw new IllegalArgumentException("null parameter passed to getAssignment");
        }
        if (!isUserAbleToViewAssignments(gradebookUid) && !currentUserHasViewOwnGradesPerm(gradebookUid)) {
            log.warn("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                    + " attempted to get assignment with id " + assignmentId);
            throw new SecurityException("You do not have permission to perform this operation");
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        Assignment assignment = (Assignment) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                return getAssignmentWithoutStats(gradebookUid, assignmentId, session);
            }
        });

        if (assignment == null) {
            throw new AssessmentNotFoundException(
                    "No gradebook item exists with gradable object id = " + assignmentId);
        }

        return getAssignmentDefinition(assignment);
    }

    @Override
    @Deprecated
    public org.sakaiproject.service.gradebook.shared.Assignment getAssignment(final String gradebookUid,
            final String assignmentName) throws AssessmentNotFoundException {
        if (assignmentName == null || gradebookUid == null) {
            throw new IllegalArgumentException("null parameter passed to getAssignment");
        }
        if (!isUserAbleToViewAssignments(gradebookUid) && !currentUserHasViewOwnGradesPerm(gradebookUid)) {
            log.warn("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                    + " attempted to get assignment " + assignmentName);
            throw new SecurityException("You do not have permission to perform this operation");
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        Assignment assignment = (Assignment) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                return getAssignmentWithoutStats(gradebookUid, assignmentName, session);
            }
        });

        if (assignment != null) {
            return getAssignmentDefinition(assignment);
        } else {
            return null;
        }
    }

    private org.sakaiproject.service.gradebook.shared.Assignment getAssignmentDefinition(
            Assignment internalAssignment) {
        org.sakaiproject.service.gradebook.shared.Assignment assignmentDefinition = new org.sakaiproject.service.gradebook.shared.Assignment();
        assignmentDefinition.setName(internalAssignment.getName());
        assignmentDefinition.setPoints(internalAssignment.getPointsPossible());
        assignmentDefinition.setDueDate(internalAssignment.getDueDate());
        assignmentDefinition.setCounted(internalAssignment.isCounted());
        assignmentDefinition.setExternallyMaintained(internalAssignment.isExternallyMaintained());
        assignmentDefinition.setExternalAppName(internalAssignment.getExternalAppName());
        assignmentDefinition.setExternalId(internalAssignment.getExternalId());
        assignmentDefinition.setReleased(internalAssignment.isReleased());
        assignmentDefinition.setId(internalAssignment.getId());
        assignmentDefinition.setExtraCredit(internalAssignment.isExtraCredit());
        if (internalAssignment.getCategory() != null) {
            assignmentDefinition.setCategoryName(internalAssignment.getCategory().getName());
            assignmentDefinition.setWeight(internalAssignment.getCategory().getWeight());
            assignmentDefinition.setCategoryExtraCredit(internalAssignment.getCategory().isExtraCredit());
            assignmentDefinition.setCategoryId(internalAssignment.getCategory().getId());
        }
        assignmentDefinition.setUngraded(internalAssignment.getUngraded());
        assignmentDefinition.setSortOrder(internalAssignment.getSortOrder());

        return assignmentDefinition;
    }

    @Override
    public GradeDefinition getGradeDefinitionForStudentForItem(final String gradebookUid, final Long assignmentId,
            final String studentUid) {

        if (gradebookUid == null || assignmentId == null || studentUid == null) {
            throw new IllegalArgumentException("Null paraemter passed to getGradeDefinitionForStudentForItem");
        }

        final boolean studentRequestingOwnScore = authn.getUserUid().equals(studentUid);

        @SuppressWarnings({ "unchecked", "rawtypes" })
        GradeDefinition gradeDef = (GradeDefinition) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {

                Assignment assignment = getAssignmentWithoutStats(gradebookUid, assignmentId, session);

                if (assignment == null) {
                    throw new AssessmentNotFoundException("There is no assignment with the assignmentId "
                            + assignmentId + " in gradebook " + gradebookUid);
                }

                if (!studentRequestingOwnScore
                        && !isUserAbleToViewItemForStudent(gradebookUid, assignment.getId(), studentUid)) {
                    log.error("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                            + " attempted to retrieve grade for student " + studentUid + " for assignment "
                            + assignmentId);
                    throw new SecurityException("You do not have permission to perform this operation");
                }

                Gradebook gradebook = assignment.getGradebook();

                GradeDefinition gradeDef = new GradeDefinition();
                gradeDef.setStudentUid(studentUid);
                gradeDef.setGradeEntryType(gradebook.getGrade_type());
                gradeDef.setGradeReleased(assignment.isReleased());

                // If this is the student, then the global setting needs to be enabled and the assignment needs to have
                // been released. Return null score information if not released
                if (studentRequestingOwnScore
                        && (!gradebook.isAssignmentsDisplayed() || !assignment.isReleased())) {
                    gradeDef.setDateRecorded(null);
                    gradeDef.setGrade(null);
                    gradeDef.setGraderUid(null);
                    gradeDef.setGradeComment(null);
                    log.debug("Student " + getUserUid() + " in gradebook " + gradebookUid
                            + " retrieving score for unreleased assignment " + assignment.getName());

                } else {

                    AssignmentGradeRecord gradeRecord = getAssignmentGradeRecord(assignment, studentUid, session);
                    CommentDefinition gradeComment = getAssignmentScoreComment(gradebookUid, assignmentId,
                            studentUid);
                    String commentText = gradeComment != null ? gradeComment.getCommentText() : null;
                    if (log.isDebugEnabled())
                        log.debug("gradeRecord=" + gradeRecord);

                    if (gradeRecord == null) {
                        gradeDef.setDateRecorded(null);
                        gradeDef.setGrade(null);
                        gradeDef.setGraderUid(null);
                        gradeDef.setGradeComment(commentText);
                    } else {
                        gradeDef.setDateRecorded(gradeRecord.getDateRecorded());
                        gradeDef.setGraderUid(gradeRecord.getGraderId());
                        gradeDef.setGradeComment(commentText);

                        if (gradebook.getGrade_type() == GradebookService.GRADE_TYPE_LETTER) {
                            List<AssignmentGradeRecord> gradeList = new ArrayList<AssignmentGradeRecord>();
                            gradeList.add(gradeRecord);
                            convertPointsToLetterGrade(gradebook, gradeList);
                            AssignmentGradeRecord gradeRec = (AssignmentGradeRecord) gradeList.get(0);
                            if (gradeRec != null) {
                                gradeDef.setGrade(gradeRec.getLetterEarned());
                            }
                        } else if (gradebook.getGrade_type() == GradebookService.GRADE_TYPE_PERCENTAGE) {
                            Double percent = calculateEquivalentPercent(assignment.getPointsPossible(),
                                    gradeRecord.getPointsEarned());
                            if (percent != null) {
                                gradeDef.setGrade(percent.toString());
                            }
                        } else {
                            if (gradeRecord.getPointsEarned() != null) {
                                gradeDef.setGrade(gradeRecord.getPointsEarned().toString());
                            }
                        }
                    }
                }

                return gradeDef;
            }
        });
        if (log.isDebugEnabled())
            log.debug("returning grade def for " + studentUid);
        return gradeDef;
    }

    @Override
    public String getGradebookDefinitionXml(String gradebookUid) {
        Long gradebookId = getGradebook(gradebookUid).getId();
        Gradebook gradebook = getGradebook(gradebookUid);

        GradebookDefinition gradebookDefinition = new GradebookDefinition();
        GradeMapping selectedGradeMapping = gradebook.getSelectedGradeMapping();
        gradebookDefinition.setSelectedGradingScaleUid(selectedGradeMapping.getGradingScale().getUid());
        gradebookDefinition.setSelectedGradingScaleBottomPercents(
                new HashMap<String, Double>(selectedGradeMapping.getGradeMap()));
        gradebookDefinition.setAssignments(getAssignments(gradebookUid));

        gradebookDefinition.setGradeType(gradebook.getGrade_type());
        gradebookDefinition.setCategoryType(gradebook.getCategory_type());
        gradebookDefinition.setCategory(getCategories(gradebookId));

        return VersionedExternalizable.toXml(gradebookDefinition);
    }

    @Override
    public GradebookInformation getGradebookInformation(String gradebookUid) {

        if (gradebookUid == null) {
            throw new IllegalArgumentException("null gradebookUid " + gradebookUid);
        }

        if (!currentUserHasEditPerm(gradebookUid) && !currentUserHasGradingPerm(gradebookUid)) {
            log.error("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                    + " attempted to access gb information");
            throw new SecurityException(
                    "You do not have permission to access gradebook information in site " + gradebookUid);
        }

        Gradebook gradebook = getGradebook(gradebookUid);
        if (gradebook == null) {
            throw new IllegalArgumentException("Their is no gradbook associated with this Id: " + gradebookUid);
        }

        GradebookInformation rval = new GradebookInformation();

        //add in all available grademappings for this gradebook
        rval.setGradeMappings(getGradebookGradeMappings(gradebook.getGradeMappings()));

        //add in details about the selected one
        GradeMapping selectedGradeMapping = gradebook.getSelectedGradeMapping();
        if (selectedGradeMapping != null) {

            rval.setSelectedGradingScaleUid(selectedGradeMapping.getGradingScale().getUid());
            rval.setSelectedGradeMappingId(Long.toString(selectedGradeMapping.getId()));

            //note that these are not the DEFAULT bottom percents but the configured ones per gradebook
            rval.setSelectedGradingScaleBottomPercents(
                    new HashMap<String, Double>(selectedGradeMapping.getGradeMap()));
            rval.setGradeScale(selectedGradeMapping.getGradingScale().getName());
        }

        rval.setGradeType(gradebook.getGrade_type());
        rval.setCategoryType(gradebook.getCategory_type());
        rval.setDisplayReleasedGradeItemsToStudents(gradebook.isAssignmentsDisplayed());

        //add in the category definitions
        rval.setCategories(this.getCategoryDefinitions(gradebookUid));

        //add in the course grade display settings
        rval.setCourseGradeDisplayed(gradebook.isCourseGradeDisplayed());
        rval.setCourseLetterGradeDisplayed(gradebook.isCourseLetterGradeDisplayed());
        rval.setCoursePointsDisplayed(gradebook.isCoursePointsDisplayed());
        rval.setCourseAverageDisplayed(gradebook.isCourseAverageDisplayed());

        return rval;
    }

    @SuppressWarnings("rawtypes")
    @Override
    public void transferGradebookDefinitionXml(String fromGradebookUid, String toGradebookUid,
            String fromGradebookXml) {
        final Gradebook gradebook = getGradebook(toGradebookUid);
        final Gradebook fromGradebook = getGradebook(fromGradebookUid);

        GradebookDefinition gradebookDefinition = (GradebookDefinition) VersionedExternalizable
                .fromXml(fromGradebookXml);

        gradebook.setCategory_type(gradebookDefinition.getCategoryType());
        gradebook.setGrade_type(gradebookDefinition.getGradeType());

        updateGradebook(gradebook);

        List category = getCategories(fromGradebook.getId());

        int assignmentsAddedCount = 0;
        Long catId = null;
        int undefined_nb = 0;

        List catList = gradebookDefinition.getCategory();
        List<Category> catList_tempt = new ArrayList<Category>();

        if (category.size() != 0) {
            //deal with category with assignments
            for (Iterator iter = category.iterator(); iter.hasNext();) {

                int categoryCount = 0;
                String catName = ((Category) iter.next()).getName();

                for (org.sakaiproject.service.gradebook.shared.Assignment obj : gradebookDefinition
                        .getAssignments()) {
                    org.sakaiproject.service.gradebook.shared.Assignment assignmentDef = (org.sakaiproject.service.gradebook.shared.Assignment) obj;

                    boolean newCategory = false;
                    // Externally managed assessments should not be included.
                    if (assignmentDef.isExternallyMaintained()) {
                        continue;
                    }

                    if (catName.equals(assignmentDef.getCategoryName())) {
                        newCategory = true;
                        categoryCount++;
                    }

                    if (assignmentDef.getCategoryName() != null) {
                        if (!newCategory) {
                        } else if (newCategory && categoryCount == 1) {
                            catId = createCategory(gradebook.getId(), assignmentDef.getCategoryName(),
                                    assignmentDef.getWeight(), 0, 0, 0, assignmentDef.isCategoryExtraCredit());
                            Category catTempt = getCategory(catId);

                            catList_tempt.add(catTempt);
                            createAssignmentForCategory(gradebook.getId(), catId, assignmentDef.getName(),
                                    assignmentDef.getPoints(), assignmentDef.getDueDate(), true, false,
                                    assignmentDef.isExtraCredit());
                            assignmentsAddedCount++;
                        } else {
                            createAssignmentForCategory(gradebook.getId(), catId, assignmentDef.getName(),
                                    assignmentDef.getPoints(), assignmentDef.getDueDate(), true, false,
                                    assignmentDef.isExtraCredit());
                            assignmentsAddedCount++;
                        }

                    }
                    //deal with assignments in undefined.
                    else {
                        if (undefined_nb == 0) {
                            createAssignment(gradebook.getId(), assignmentDef.getName(), assignmentDef.getPoints(),
                                    assignmentDef.getDueDate(), true, false, assignmentDef.isExtraCredit());
                            assignmentsAddedCount++;

                        }
                    }
                }
                undefined_nb++;
            }

            //deal with Category without assignments inside
            Iterator it_tempt = catList_tempt.iterator();
            Iterator it = catList.iterator();

            Category cat_cat;
            Category cat_tempt;

            while (it_tempt.hasNext()) {
                cat_tempt = (Category) it_tempt.next();
                while (it.hasNext()) {
                    cat_cat = (Category) it.next();
                    if (cat_tempt.getName().equals(cat_cat.getName())) {
                        it.remove();
                        it = catList.iterator();
                    }
                }
                it = catList.iterator();
            }

            Iterator itUpdate = catList.iterator();
            while (itUpdate.hasNext()) {
                Category catObj = (Category) itUpdate.next();
                createCategory(gradebook.getId(), catObj.getName(), catObj.getWeight(), catObj.getDrop_lowest(),
                        catObj.getDropHighest(), catObj.getKeepHighest(), catObj.isExtraCredit());
            }
        }
        //deal with no categories
        else {
            for (org.sakaiproject.service.gradebook.shared.Assignment obj : gradebookDefinition.getAssignments()) {
                org.sakaiproject.service.gradebook.shared.Assignment assignmentDef = (org.sakaiproject.service.gradebook.shared.Assignment) obj;

                // Externally managed assessments should not be included.
                if (assignmentDef.isExternallyMaintained()) {
                    continue;
                }

                // All assignments should be unreleased even if they were released in the original.

                createAssignment(gradebook.getId(), assignmentDef.getName(), assignmentDef.getPoints(),
                        assignmentDef.getDueDate(), true, false, assignmentDef.isExtraCredit());
                assignmentsAddedCount++;
            }

        }

        if (log.isInfoEnabled())
            log.info("Merge to gradebook " + toGradebookUid + " added " + assignmentsAddedCount + " assignments");

        // Carry over the old gradebook's selected grading scheme if possible.
        String fromGradingScaleUid = gradebookDefinition.getSelectedGradingScaleUid();
        MERGE_GRADE_MAPPING: if (!StringUtils.isEmpty(fromGradingScaleUid)) {
            for (GradeMapping gradeMapping : gradebook.getGradeMappings()) {
                if (gradeMapping.getGradingScale().getUid().equals(fromGradingScaleUid)) {
                    // We have a match. Now make sure that the grades are as expected.
                    Map<String, Double> inputGradePercents = gradebookDefinition
                            .getSelectedGradingScaleBottomPercents();
                    Set<String> gradeCodes = (Set<String>) inputGradePercents.keySet();
                    if (gradeCodes.containsAll(gradeMapping.getGradeMap().keySet())) {
                        // Modify the existing grade-to-percentage map.
                        for (String gradeCode : gradeCodes) {
                            gradeMapping.getGradeMap().put(gradeCode, inputGradePercents.get(gradeCode));
                        }
                        gradebook.setSelectedGradeMapping(gradeMapping);
                        updateGradebook(gradebook);
                        if (log.isInfoEnabled())
                            log.info("Merge to gradebook " + toGradebookUid + " updated grade mapping");
                    } else {
                        if (log.isInfoEnabled())
                            log.info("Merge to gradebook " + toGradebookUid
                                    + " skipped grade mapping change because the " + fromGradingScaleUid
                                    + " grade codes did not match");
                    }
                    break MERGE_GRADE_MAPPING;
                }
            }
            // Did not find a matching grading scale.
            if (log.isInfoEnabled())
                log.info("Merge to gradebook " + toGradebookUid
                        + " skipped grade mapping change because grading scale " + fromGradingScaleUid
                        + " is not defined");
        }
    }

    @Override
    public void mergeGradebookDefinitionXml(String toGradebookUid, String fromGradebookXml) {
        final Gradebook gradebook = getGradebook(toGradebookUid);
        GradebookDefinition gradebookDefinition = (GradebookDefinition) VersionedExternalizable
                .fromXml(fromGradebookXml);

        @SuppressWarnings({ "unchecked", "rawtypes" })
        List<String> assignmentNames = (List<String>) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(final Session session) throws HibernateException {
                return session.createQuery(
                        "select asn.name from Assignment as asn where asn.gradebook.id=? and asn.removed=false")
                        .setLong(0, gradebook.getId().longValue()).list();
            }
        });

        // Add any non-externally-managed assignments with non-duplicate names.
        int assignmentsAddedCount = 0;
        for (org.sakaiproject.service.gradebook.shared.Assignment obj : gradebookDefinition.getAssignments()) {
            org.sakaiproject.service.gradebook.shared.Assignment assignmentDef = (org.sakaiproject.service.gradebook.shared.Assignment) obj;

            // Externally managed assessments should not be included.
            if (assignmentDef.isExternallyMaintained()) {
                continue;
            }

            // Skip any input assignments with duplicate names.
            if (assignmentNames.contains(assignmentDef.getName())) {
                if (log.isInfoEnabled())
                    log.info("Merge to gradebook " + toGradebookUid + " skipped duplicate assignment named "
                            + assignmentDef.getName());
                continue;
            }

            // All assignments should be unreleased even if they were released in the original.
            createAssignment(gradebook.getId(), assignmentDef.getName(), assignmentDef.getPoints(),
                    assignmentDef.getDueDate(), true, false, assignmentDef.isExtraCredit());
            assignmentsAddedCount++;
        }
        if (log.isInfoEnabled())
            log.info("Merge to gradebook " + toGradebookUid + " added " + assignmentsAddedCount + " assignments");

        // Carry over the old gradebook's selected grading scheme if possible.
        String fromGradingScaleUid = gradebookDefinition.getSelectedGradingScaleUid();
        MERGE_GRADE_MAPPING: if (!StringUtils.isEmpty(fromGradingScaleUid)) {
            for (GradeMapping gradeMapping : gradebook.getGradeMappings()) {
                if (gradeMapping.getGradingScale().getUid().equals(fromGradingScaleUid)) {
                    // We have a match. Now make sure that the grades are as expected.
                    Map<String, Double> inputGradePercents = gradebookDefinition
                            .getSelectedGradingScaleBottomPercents();
                    Set<String> gradeCodes = (Set<String>) inputGradePercents.keySet();
                    if (gradeCodes.containsAll(gradeMapping.getGradeMap().keySet())) {
                        // Modify the existing grade-to-percentage map.
                        for (String gradeCode : gradeCodes) {
                            gradeMapping.getGradeMap().put(gradeCode, inputGradePercents.get(gradeCode));
                        }
                        gradebook.setSelectedGradeMapping(gradeMapping);
                        updateGradebook(gradebook);
                        if (log.isInfoEnabled())
                            log.info("Merge to gradebook " + toGradebookUid + " updated grade mapping");
                    } else {
                        if (log.isInfoEnabled())
                            log.info("Merge to gradebook " + toGradebookUid
                                    + " skipped grade mapping change because the " + fromGradingScaleUid
                                    + " grade codes did not match");
                    }
                    break MERGE_GRADE_MAPPING;
                }
            }
            // Did not find a matching grading scale.
            if (log.isInfoEnabled())
                log.info("Merge to gradebook " + toGradebookUid
                        + " skipped grade mapping change because grading scale " + fromGradingScaleUid
                        + " is not defined");
        }
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public void removeAssignment(final Long assignmentId) throws StaleObjectModificationException {

        HibernateCallback hc = new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Assignment asn = (Assignment) session.load(Assignment.class, assignmentId);
                Gradebook gradebook = asn.getGradebook();
                asn.setRemoved(true);
                session.update(asn);

                if (log.isInfoEnabled())
                    log.info("Assignment " + asn.getName() + " has been removed from " + gradebook);
                return null;
            }
        };
        getHibernateTemplate().execute(hc);

    }

    @Override
    public Long addAssignment(String gradebookUid,
            org.sakaiproject.service.gradebook.shared.Assignment assignmentDefinition) {
        if (!getAuthz().isUserAbleToEditAssessments(gradebookUid)) {
            log.error("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                    + " attempted to add an assignment");
            throw new SecurityException("You do not have permission to perform this operation");
        }

        // Ensure that points is > zero.
        Double points = assignmentDefinition.getPoints();
        if ((points == null) || (points.doubleValue() <= 0)) {
            throw new AssignmentHasIllegalPointsException("Points must be > 0");
        }

        Gradebook gradebook = getGradebook(gradebookUid);

        //if attaching to category
        if (assignmentDefinition.getCategoryId() != null) {
            return createAssignmentForCategory(gradebook.getId(), assignmentDefinition.getCategoryId(),
                    assignmentDefinition.getName(), points, assignmentDefinition.getDueDate(),
                    !assignmentDefinition.isCounted(), assignmentDefinition.isReleased(),
                    assignmentDefinition.isExtraCredit());
        }

        return createAssignment(gradebook.getId(), assignmentDefinition.getName(), points,
                assignmentDefinition.getDueDate(), !assignmentDefinition.isCounted(),
                assignmentDefinition.isReleased(), assignmentDefinition.isExtraCredit());
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    @Override
    public void updateAssignment(final String gradebookUid, final Long assignmentId,
            final org.sakaiproject.service.gradebook.shared.Assignment assignmentDefinition) {
        if (!getAuthz().isUserAbleToEditAssessments(gradebookUid)) {
            log.error("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                    + " attempted to change the definition of assignment " + assignmentId);
            throw new SecurityException("You do not have permission to perform this operation");
        }

        // This method is for Gradebook-managed assignments only.
        if (assignmentDefinition.isExternallyMaintained()) {
            log.error("User " + getUserUid() + " in gradebook " + gradebookUid + " attempted to set assignment "
                    + assignmentId + " to be externally maintained");
            throw new SecurityException("You do not have permission to perform this operation");
        }

        getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Assignment assignment = getAssignmentWithoutStats(gradebookUid, assignmentId, session);
                if (assignment == null) {
                    throw new AssessmentNotFoundException(
                            "There is no assignment with id " + assignmentId + " in gradebook " + gradebookUid);
                }
                if (assignment.isExternallyMaintained()) {
                    log.error("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                            + " attempted to change the definition of externally maintained assignment "
                            + assignmentId);
                    throw new SecurityException("You do not have permission to perform this operation");
                }
                assignment.setCounted(assignmentDefinition.isCounted());
                assignment.setDueDate(assignmentDefinition.getDueDate());
                assignment.setName(assignmentDefinition.getName().trim());
                assignment.setPointsPossible(assignmentDefinition.getPoints());
                assignment.setReleased(assignmentDefinition.isReleased());
                assignment.setExtraCredit(assignmentDefinition.isExtraCredit());

                //if we have a category, get it and set it
                if (assignmentDefinition.getCategoryId() != null) {
                    Category cat = (Category) session.load(Category.class, assignmentDefinition.getCategoryId());
                    assignment.setCategory(cat);
                }

                updateAssignment(assignment, session);
                return null;
            }
        });
    }

    @Override
    public Map<String, String> getImportCourseGrade(String gradebookUid) {
        return getImportCourseGrade(gradebookUid, true, true);
    }

    @Override
    public Map<String, String> getImportCourseGrade(String gradebookUid, boolean useDefault) {
        return getImportCourseGrade(gradebookUid, useDefault, true);
    }

    @Override
    public Map<String, String> getImportCourseGrade(String gradebookUid, boolean useDefault, boolean mapTheGrades) {
        HashMap<String, String> returnMap = new HashMap<String, String>();

        try {
            //There is a new permission for course grade visibility for TA's as part of GradebookNG.
            //However the permission cannot be added here as it is not backwards compatible with Gradebook classique
            //  and would mean that all existing permissions need to be updated to add it.
            //See GradebookNgBusinessService.isCourseGradeVisible.
            //At some point it should be migrated and a DB conversion performed.

            Gradebook thisGradebook = getGradebook(gradebookUid);

            List assignList = getAssignmentsCounted(thisGradebook.getId());
            boolean nonAssignment = false;
            if (assignList == null || assignList.size() < 1) {
                nonAssignment = true;
            }

            Long gradebookId = thisGradebook.getId();
            CourseGrade courseGrade = getCourseGrade(gradebookId);

            Map viewableEnrollmentsMap = authz.findMatchingEnrollmentsForViewableCourseGrade(gradebookUid,
                    thisGradebook.getCategory_type(), null, null);
            Map<String, EnrollmentRecord> enrollmentMap = new HashMap<String, EnrollmentRecord>();

            Map<String, EnrollmentRecord> enrollmentMapUid = new HashMap<String, EnrollmentRecord>();
            for (Iterator iter = viewableEnrollmentsMap.keySet().iterator(); iter.hasNext();) {
                EnrollmentRecord enr = (EnrollmentRecord) iter.next();
                enrollmentMap.put(enr.getUser().getUserUid(), enr);
                enrollmentMapUid.put(enr.getUser().getUserUid(), enr);
            }
            List gradeRecords = getPointsEarnedCourseGradeRecords(courseGrade, enrollmentMap.keySet());
            for (Iterator iter = gradeRecords.iterator(); iter.hasNext();) {
                CourseGradeRecord gradeRecord = (CourseGradeRecord) iter.next();

                GradeMapping gradeMap = thisGradebook.getSelectedGradeMapping();

                EnrollmentRecord enr = enrollmentMapUid.get(gradeRecord.getStudentId());
                if (enr != null) {
                    // SAK-29243: if we are not mapping grades, we don't want letter grade here
                    if (mapTheGrades && StringUtils.isNotBlank(gradeRecord.getEnteredGrade())) {
                        returnMap.put(enr.getUser().getDisplayId(), gradeRecord.getEnteredGrade());
                    } else {
                        if (!nonAssignment) {
                            Double grade = null;

                            if (useDefault) {
                                grade = gradeRecord.getNonNullAutoCalculatedGrade();
                            } else {
                                grade = gradeRecord.getAutoCalculatedGrade();
                            }

                            if (mapTheGrades) {
                                returnMap.put(enr.getUser().getDisplayId(), (String) gradeMap.getGrade(grade));
                            } else {
                                returnMap.put(enr.getUser().getDisplayId(), grade.toString());
                            }

                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("Error in getImportCourseGrade", e);
        }
        return returnMap;
    }

    @Override
    public CourseGrade getCourseGrade(Long gradebookId) {
        return (CourseGrade) getHibernateTemplate()
                .find("from CourseGrade as cg where cg.gradebook.id=?", gradebookId).get(0);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public List getPointsEarnedCourseGradeRecords(final CourseGrade courseGrade, final Collection studentUids) {
        HibernateCallback hc = new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                if (studentUids == null || studentUids.size() == 0) {
                    if (log.isInfoEnabled())
                        log.info("Returning no grade records for an empty collection of student UIDs");
                    return new ArrayList();
                }

                Query q = session
                        .createQuery("from CourseGradeRecord as cgr where cgr.gradableObject.id=:gradableObjectId");
                q.setLong("gradableObjectId", courseGrade.getId().longValue());
                List records = filterAndPopulateCourseGradeRecordsByStudents(courseGrade, q.list(), studentUids);

                Long gradebookId = courseGrade.getGradebook().getId();
                Gradebook gradebook = getGradebook(gradebookId);
                List cates = getCategories(gradebookId);

                // get all of the AssignmentGradeRecords here to avoid repeated db calls
                Map<String, List<AssignmentGradeRecord>> gradeRecMap = getGradeRecordMapForStudents(session,
                        gradebookId, studentUids);

                // get all of the counted assignments
                List<Assignment> assignments = getCountedAssignments(session, gradebookId);
                List<Assignment> countedAssigns = new ArrayList<Assignment>();
                if (assignments != null) {
                    for (Assignment assign : assignments) {
                        // extra check to account for new features like extra credit
                        if (assign.isIncludedInCalculations()) {
                            countedAssigns.add(assign);
                        }
                    }
                }
                //double totalPointsPossible = getTotalPointsInternal(gradebookId, session);
                //if(log.isDebugEnabled()) log.debug("Total points = " + totalPointsPossible);

                for (Iterator iter = records.iterator(); iter.hasNext();) {
                    CourseGradeRecord cgr = (CourseGradeRecord) iter.next();
                    //double totalPointsEarned = getTotalPointsEarnedInternal(gradebookId, cgr.getStudentId(), session);
                    List<AssignmentGradeRecord> studentGradeRecs = gradeRecMap.get(cgr.getStudentId());

                    applyDropScores(studentGradeRecs);
                    List totalEarned = getTotalPointsEarnedInternal(cgr.getStudentId(), gradebook, cates,
                            studentGradeRecs, countedAssigns);
                    double totalPointsEarned = ((Double) totalEarned.get(0)).doubleValue();
                    double literalTotalPointsEarned = ((Double) totalEarned.get(1)).doubleValue();
                    double totalPointsPossible = getTotalPointsInternal(gradebook, cates, cgr.getStudentId(),
                            studentGradeRecs, countedAssigns, false);
                    cgr.initNonpersistentFields(totalPointsPossible, totalPointsEarned, literalTotalPointsEarned);
                    if (log.isDebugEnabled())
                        log.debug("Points earned = " + cgr.getPointsEarned());
                }

                return records;
            }
        };
        return (List) getHibernateTemplate().execute(hc);
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    private List filterAndPopulateCourseGradeRecordsByStudents(CourseGrade courseGrade, Collection gradeRecords,
            Collection studentUids) {
        List filteredRecords = new ArrayList();
        Set missingStudents = new HashSet(studentUids);
        for (Iterator iter = gradeRecords.iterator(); iter.hasNext();) {
            CourseGradeRecord cgr = (CourseGradeRecord) iter.next();
            if (studentUids.contains(cgr.getStudentId())) {
                filteredRecords.add(cgr);
                missingStudents.remove(cgr.getStudentId());
            }
        }
        for (Iterator iter = missingStudents.iterator(); iter.hasNext();) {
            String studentUid = (String) iter.next();
            CourseGradeRecord cgr = new CourseGradeRecord(courseGrade, studentUid);
            filteredRecords.add(cgr);
        }
        return filteredRecords;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private double getTotalPointsInternal(final Gradebook gradebook, final List categories, final String studentId,
            List<AssignmentGradeRecord> studentGradeRecs, List<Assignment> countedAssigns, boolean literalTotal) {
        int gbGradeType = gradebook.getGrade_type();
        if (gbGradeType != GradebookService.GRADE_TYPE_POINTS
                && gbGradeType != GradebookService.GRADE_TYPE_PERCENTAGE) {
            if (log.isInfoEnabled())
                log.error("Wrong grade type in GradebookCalculationImpl.getTotalPointsInternal");
            return -1;
        }

        if (studentGradeRecs == null || countedAssigns == null) {
            if (log.isDebugEnabled())
                log.debug("Returning 0 from getTotalPointsInternal "
                        + "since studentGradeRecs or countedAssigns was null");
            return 0;
        }

        double totalPointsPossible = 0;

        HashSet<Assignment> countedSet = new HashSet<Assignment>(countedAssigns);

        // we need to filter this list to identify only "counted" grade recs
        List<AssignmentGradeRecord> countedGradeRecs = new ArrayList<AssignmentGradeRecord>();
        for (AssignmentGradeRecord gradeRec : studentGradeRecs) {
            Assignment assign = gradeRec.getAssignment();
            boolean extraCredit = assign.isExtraCredit();
            if (gradebook.getCategory_type() != GradebookService.CATEGORY_TYPE_NO_CATEGORY
                    && assign.getCategory() != null && assign.getCategory().isExtraCredit())
                extraCredit = true;

            if (assign.isCounted() && !assign.getUngraded() && !assign.isRemoved() && countedSet.contains(assign)
                    && assign.getPointsPossible() != null && assign.getPointsPossible() > 0
                    && !gradeRec.getDroppedFromGrade() && !extraCredit) {
                countedGradeRecs.add(gradeRec);
            }
        }

        Set assignmentsTaken = new HashSet();
        Set categoryTaken = new HashSet();
        for (AssignmentGradeRecord gradeRec : countedGradeRecs) {
            if (gradeRec.getPointsEarned() != null && !gradeRec.getPointsEarned().equals("")) {
                Double pointsEarned = new Double(gradeRec.getPointsEarned());
                Assignment go = gradeRec.getAssignment();
                if (pointsEarned != null) {
                    if (gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_NO_CATEGORY) {
                        assignmentsTaken.add(go.getId());
                    } else if ((gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_ONLY_CATEGORY
                            || gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_WEIGHTED_CATEGORY)
                            && go != null && categories != null) {
                        //                              assignmentsTaken.add(go.getId());
                        //                          }
                        //                          else if(gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_WEIGHTED_CATEGORY && go != null && categories != null)
                        //                          {
                        for (int i = 0; i < categories.size(); i++) {
                            Category cate = (Category) categories.get(i);
                            if (cate != null && !cate.isRemoved() && go.getCategory() != null
                                    && cate.getId().equals(go.getCategory().getId())
                                    && ((cate.isExtraCredit() != null && !cate.isExtraCredit())
                                            || cate.isExtraCredit() == null)) {
                                assignmentsTaken.add(go.getId());
                                categoryTaken.add(cate.getId());
                                break;
                            }
                        }
                    }
                }
            }
        }

        if (!assignmentsTaken.isEmpty()) {
            if (!literalTotal && gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_WEIGHTED_CATEGORY) {
                for (int i = 0; i < categories.size(); i++) {
                    Category cate = (Category) categories.get(i);
                    if (cate != null && !cate.isRemoved() && categoryTaken.contains(cate.getId())) {
                        totalPointsPossible += cate.getWeight().doubleValue();
                    }
                }
                return totalPointsPossible;
            }
            Iterator assignmentIter = countedAssigns.iterator();
            while (assignmentIter.hasNext()) {
                Assignment asn = (Assignment) assignmentIter.next();
                if (asn != null) {
                    Double pointsPossible = asn.getPointsPossible();

                    if (gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_NO_CATEGORY
                            && assignmentsTaken.contains(asn.getId())) {
                        totalPointsPossible += pointsPossible.doubleValue();
                    } else if (gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_ONLY_CATEGORY
                            && assignmentsTaken.contains(asn.getId())) {
                        totalPointsPossible += pointsPossible.doubleValue();
                    } else if (literalTotal
                            && gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_WEIGHTED_CATEGORY
                            && assignmentsTaken.contains(asn.getId())) {
                        totalPointsPossible += pointsPossible.doubleValue();
                    }
                }
            }
        } else
            totalPointsPossible = -1;

        return totalPointsPossible;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private List getTotalPointsEarnedInternal(final String studentId, final Gradebook gradebook,
            final List categories, final List<AssignmentGradeRecord> gradeRecs, List<Assignment> countedAssigns) {
        int gbGradeType = gradebook.getGrade_type();
        if (gbGradeType != GradebookService.GRADE_TYPE_POINTS
                && gbGradeType != GradebookService.GRADE_TYPE_PERCENTAGE) {
            if (log.isInfoEnabled())
                log.error("Wrong grade type in GradebookCalculationImpl.getTotalPointsEarnedInternal");
            return new ArrayList();
        }

        if (gradeRecs == null || countedAssigns == null) {
            if (log.isDebugEnabled())
                log.debug("getTotalPointsEarnedInternal for " + "studentId=" + studentId
                        + " returning 0 because null gradeRecs or countedAssigns");
            List returnList = new ArrayList();
            returnList.add(new Double(0));
            returnList.add(new Double(0));
            returnList.add(new Double(0)); // 3rd one is for the pre-adjusted course grade
            return returnList;
        }

        double totalPointsEarned = 0;
        BigDecimal literalTotalPointsEarned = new BigDecimal(0d);

        Map cateScoreMap = new HashMap();
        Map cateTotalScoreMap = new HashMap();

        Set assignmentsTaken = new HashSet();
        for (AssignmentGradeRecord gradeRec : gradeRecs) {
            if (gradeRec.getPointsEarned() != null && !gradeRec.getPointsEarned().equals("")
                    && !gradeRec.getDroppedFromGrade()) {
                Assignment go = gradeRec.getAssignment();
                if (go.isIncludedInCalculations() && countedAssigns.contains(go)) {
                    Double pointsEarned = new Double(gradeRec.getPointsEarned());
                    //if(gbGradeType == GradebookService.GRADE_TYPE_POINTS)
                    //{
                    if (gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_NO_CATEGORY) {
                        totalPointsEarned += pointsEarned.doubleValue();
                        literalTotalPointsEarned = (new BigDecimal(pointsEarned.doubleValue()))
                                .add(literalTotalPointsEarned);
                        assignmentsTaken.add(go.getId());
                    } else if (gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_ONLY_CATEGORY
                            && go != null) {
                        totalPointsEarned += pointsEarned.doubleValue();
                        literalTotalPointsEarned = (new BigDecimal(pointsEarned.doubleValue()))
                                .add(literalTotalPointsEarned);
                        assignmentsTaken.add(go.getId());
                    } else if (gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_WEIGHTED_CATEGORY
                            && go != null && categories != null) {
                        for (int i = 0; i < categories.size(); i++) {
                            Category cate = (Category) categories.get(i);
                            if (cate != null && !cate.isRemoved() && go.getCategory() != null
                                    && cate.getId().equals(go.getCategory().getId())) {
                                assignmentsTaken.add(go.getId());
                                literalTotalPointsEarned = (new BigDecimal(pointsEarned.doubleValue()))
                                        .add(literalTotalPointsEarned);
                                if (cateScoreMap.get(cate.getId()) != null) {
                                    cateScoreMap.put(cate.getId(),
                                            new Double(((Double) cateScoreMap.get(cate.getId())).doubleValue()
                                                    + pointsEarned.doubleValue()));
                                } else {
                                    cateScoreMap.put(cate.getId(), new Double(pointsEarned));
                                }
                                break;
                            }
                        }
                    }
                }
            }
        }

        if (gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_WEIGHTED_CATEGORY
                && categories != null) {
            Iterator assgnsIter = countedAssigns.iterator();
            while (assgnsIter.hasNext()) {
                Assignment asgn = (Assignment) assgnsIter.next();
                if (assignmentsTaken.contains(asgn.getId())) {
                    for (int i = 0; i < categories.size(); i++) {
                        Category cate = (Category) categories.get(i);
                        if (cate != null && !cate.isRemoved() && asgn.getCategory() != null
                                && cate.getId().equals(asgn.getCategory().getId()) && !asgn.isExtraCredit()) {

                            if (cateTotalScoreMap.get(cate.getId()) == null) {
                                cateTotalScoreMap.put(cate.getId(), asgn.getPointsPossible());
                            } else {
                                cateTotalScoreMap.put(cate.getId(),
                                        new Double(((Double) cateTotalScoreMap.get(cate.getId())).doubleValue()
                                                + asgn.getPointsPossible().doubleValue()));
                            }

                        }
                    }
                }
            }
        }

        if (assignmentsTaken.isEmpty())
            totalPointsEarned = -1;

        if (gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_WEIGHTED_CATEGORY) {
            for (int i = 0; i < categories.size(); i++) {
                Category cate = (Category) categories.get(i);
                if (cate != null && !cate.isRemoved() && cateScoreMap.get(cate.getId()) != null
                        && cateTotalScoreMap.get(cate.getId()) != null) {
                    totalPointsEarned += ((Double) cateScoreMap.get(cate.getId())).doubleValue()
                            * cate.getWeight().doubleValue()
                            / ((Double) cateTotalScoreMap.get(cate.getId())).doubleValue();
                }
            }
        }

        if (log.isDebugEnabled())
            log.debug(
                    "getTotalPointsEarnedInternal for studentId=" + studentId + " returning " + totalPointsEarned);
        List returnList = new ArrayList();
        returnList.add(new Double(totalPointsEarned));
        returnList.add(
                new Double((new BigDecimal(literalTotalPointsEarned.doubleValue(), GradebookService.MATH_CONTEXT))
                        .doubleValue()));

        return returnList;
    }

    /**
     * Internal method to get a gradebook based on its id.
     * @param id
     * @return
     * 
     * NOTE: When the UI changes, this is to be turned private again
     */
    public Gradebook getGradebook(Long id) {
        return (Gradebook) getHibernateTemplate().load(Gradebook.class, id);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private List getAssignmentsCounted(final Long gradebookId) throws HibernateException {
        HibernateCallback hc = new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                List assignments = session.createQuery(
                        "from Assignment as asn where asn.gradebook.id=? and asn.removed=false and asn.notCounted=false")
                        .setLong(0, gradebookId.longValue()).list();
                return assignments;
            }
        };
        return (List) getHibernateTemplate().execute(hc);
    }

    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public boolean checkStudentsNotSubmitted(String gradebookUid) {
        Gradebook gradebook = getGradebook(gradebookUid);
        Set studentUids = getAllStudentUids(getGradebookUid(gradebook.getId()));
        if (gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_NO_CATEGORY
                || gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_ONLY_CATEGORY) {
            List records = getAllAssignmentGradeRecords(gradebook.getId(), studentUids);
            List assigns = getAssignments(gradebook.getId(), SortType.SORT_BY_SORTING, true);
            List filteredAssigns = new ArrayList();
            for (Iterator iter = assigns.iterator(); iter.hasNext();) {
                Assignment assignment = (Assignment) iter.next();
                if (assignment.isCounted() && !assignment.getUngraded())
                    filteredAssigns.add(assignment);
            }
            List filteredRecords = new ArrayList();
            for (Iterator iter = records.iterator(); iter.hasNext();) {
                AssignmentGradeRecord agr = (AssignmentGradeRecord) iter.next();
                if (!agr.isCourseGradeRecord() && agr.getAssignment().isCounted()
                        && !agr.getAssignment().getUngraded()) {
                    if (agr.getPointsEarned() == null)
                        return true;
                    filteredRecords.add(agr);
                }
            }

            if (filteredRecords.size() < (filteredAssigns.size() * studentUids.size()))
                return true;

            return false;
        } else {
            List assigns = getAssignments(gradebook.getId(), SortType.SORT_BY_SORTING, true);
            List records = getAllAssignmentGradeRecords(gradebook.getId(), studentUids);
            Set filteredAssigns = new HashSet();
            for (Iterator iter = assigns.iterator(); iter.hasNext();) {
                Assignment assign = (Assignment) iter.next();
                if (assign != null && assign.isCounted() && !assign.getUngraded()) {
                    if (assign.getCategory() != null && !assign.getCategory().isRemoved()) {
                        filteredAssigns.add(assign.getId());
                    }
                }
            }

            List filteredRecords = new ArrayList();
            for (Iterator iter = records.iterator(); iter.hasNext();) {
                AssignmentGradeRecord agr = (AssignmentGradeRecord) iter.next();
                if (filteredAssigns.contains(agr.getAssignment().getId()) && !agr.isCourseGradeRecord()) {
                    if (agr.getPointsEarned() == null)
                        return true;
                    filteredRecords.add(agr);
                }
            }

            if (filteredRecords.size() < filteredAssigns.size() * studentUids.size())
                return true;

            return false;
        }
    }

    /**
     * Get all assignment grade records for the given students
     * 
     * @param gradebookId
     * @param studentUids
     * @return
     * 
     * NOTE When the UI changes, this needs to be made private again
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List getAllAssignmentGradeRecords(final Long gradebookId, final Collection studentUids) {
        HibernateCallback hc = new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                if (studentUids.size() == 0) {
                    // If there are no enrollments, no need to execute the query.
                    if (log.isInfoEnabled())
                        log.info("No enrollments were specified.  Returning an empty List of grade records");
                    return new ArrayList();
                } else {
                    Query q = session.createQuery(
                            "from AssignmentGradeRecord as agr where agr.gradableObject.removed=false and "
                                    + "agr.gradableObject.gradebook.id=:gradebookId order by agr.pointsEarned");
                    q.setLong("gradebookId", gradebookId.longValue());
                    return filterGradeRecordsByStudents(q.list(), studentUids);
                }
            }
        };
        return (List) getHibernateTemplate().execute(hc);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    private List getAllAssignmentGradeRecordsForGbItem(final Long gradableObjectId, final Collection studentUids) {
        HibernateCallback hc = new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                if (studentUids.size() == 0) {
                    // If there are no enrollments, no need to execute the query.
                    if (log.isInfoEnabled())
                        log.info("No enrollments were specified.  Returning an empty List of grade records");
                    return new ArrayList();
                } else {
                    Query q = session.createQuery(
                            "from AssignmentGradeRecord as agr where agr.gradableObject.removed=false and "
                                    + "agr.gradableObject.id=:gradableObjectId order by agr.pointsEarned");
                    q.setLong("gradableObjectId", gradableObjectId.longValue());
                    return filterGradeRecordsByStudents(q.list(), studentUids);
                }
            }
        };
        return (List) getHibernateTemplate().execute(hc);
    }

    /**
     * Gets all AssignmentGradeRecords on the gradableObjectIds limited to students specified by studentUids
     */
    private List<AssignmentGradeRecord> getAllAssignmentGradeRecordsForGbItems(final List<Long> gradableObjectIds,
            final List studentUids) {
        HibernateCallback hc = new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                List<AssignmentGradeRecord> gradeRecords = new ArrayList<AssignmentGradeRecord>();
                if (studentUids.size() == 0) {
                    // If there are no enrollments, no need to execute the query.
                    if (log.isDebugEnabled())
                        log.debug("No enrollments were specified. Returning an empty List of grade records");
                    return gradeRecords;
                }
                /*
                 * Watch out for Oracle's "in" limit. Ignoring oracle, the query would be:
                 * "from AssignmentGradeRecord as agr where agr.gradableObject.removed = false and agr.gradableObject.id in (:gradableObjectIds) and agr.studentId in (:studentUids)"
                 * Note: the order is not important. The calling methods will iterate over all entries and add them to a map.
                 * We could have made this method return a map, but we'd have to iterate over the items in order to add them to the map anyway.
                 * That would be a waste of a loop that the calling method could use to perform additional tasks.
                 */
                // For Oracle, iterate over gbItems 1000 at a time (sympathies to whoever needs to query grades for a thousand gbItems)
                int minGbo = 0;
                int maxGbo = Math.min(gradableObjectIds.size(), 1000);
                while (minGbo < gradableObjectIds.size()) {
                    // For Oracle, iterate over students 1000 at a time
                    int minStudent = 0;
                    int maxStudent = Math.min(studentUids.size(), 1000);
                    while (minStudent < studentUids.size()) {
                        Query q = session.createQuery(
                                "from AssignmentGradeRecord as agr where agr.gradableObject.removed = false and "
                                        + "agr.gradableObject.id in (:gradableObjectIds) and agr.studentId in (:studentUids)");
                        q.setParameterList("gradableObjectIds", gradableObjectIds.subList(minGbo, maxGbo));
                        q.setParameterList("studentUids", studentUids.subList(minStudent, maxStudent));
                        // Add the query results to our overall results (in case there's over a thousand things)
                        gradeRecords.addAll(q.list());
                        minStudent += 1000;
                        maxStudent = Math.min(studentUids.size(), minStudent + 1000);
                    }
                    minGbo += 1000;
                    maxGbo = Math.min(gradableObjectIds.size(), minGbo + 1000);
                }
                return gradeRecords;
            }
        };
        return (List<AssignmentGradeRecord>) getHibernateTemplate().execute(hc);
    }

    /**
     * Get a list of assignments, sorted
     * @param gradebookId
     * @param sortBy
     * @param ascending
     * @return
     * 
     * NOTE: When the UI changes, this needs to go back to private
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List getAssignments(final Long gradebookId, final SortType sortBy, final boolean ascending) {
        return (List) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                List assignments = getAssignments(gradebookId, session);

                sortAssignments(assignments, sortBy, ascending);
                return assignments;
            }
        });
    }

    /**
     * Sort the list of (internal) assignments by the given criteria
     * @param assignments
     * @param sortBy
     * @param ascending
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    private void sortAssignments(List assignments, SortType sortBy, boolean ascending) {

        //note, this is duplicated in the tool GradebookManagerHibernateImpl class  
        Comparator comp;

        if (sortBy == null) {
            sortBy = SortType.SORT_BY_SORTING; //default
        }

        switch (sortBy) {

        case SORT_BY_NONE:
            return; //no sorting
        case SORT_BY_NAME:
            comp = GradableObject.nameComparator;
            break;
        case SORT_BY_DATE:
            comp = GradableObject.dateComparator;
            break;
        case SORT_BY_MEAN:
            comp = GradableObject.meanComparator;
            break;
        case SORT_BY_POINTS:
            comp = Assignment.pointsComparator;
            break;
        case SORT_BY_RELEASED:
            comp = Assignment.releasedComparator;
            break;
        case SORT_BY_COUNTED:
            comp = Assignment.countedComparator;
            break;
        case SORT_BY_EDITOR:
            comp = Assignment.gradeEditorComparator;
            break;
        case SORT_BY_SORTING:
            comp = Assignment.sortingComparator;
            break;
        default:
            comp = GradableObject.defaultComparator;
        }

        Collections.sort(assignments, comp);
        if (!ascending) {
            Collections.reverse(assignments);
        }
        if (log.isDebugEnabled()) {
            log.debug("sortAssignments: ordering by " + sortBy + " (" + comp + "), ascending=" + ascending);
        }
    }

    /*
     * (non-Javadoc)
     * @see org.sakaiproject.service.gradebook.shared.GradebookService#getViewableAssignmentsForCurrentUser(java.lang.String)
     */
    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List<org.sakaiproject.service.gradebook.shared.Assignment> getViewableAssignmentsForCurrentUser(
            String gradebookUid) throws GradebookNotFoundException {

        List<Assignment> viewableAssignments = new ArrayList<>();
        SortedSet<org.sakaiproject.service.gradebook.shared.Assignment> assignmentsToReturn = new TreeSet<>();

        Gradebook gradebook = getGradebook(gradebookUid);

        // will send back all assignments if user can grade all
        if (getAuthz().isUserAbleToGradeAll(gradebookUid)) {
            viewableAssignments = getAssignments(gradebook.getId(), SortType.SORT_BY_SORTING, true);
        } else if (getAuthz().isUserAbleToGrade(gradebookUid)) {
            // if user can grade and doesn't have grader perm restrictions, they
            // may view all assigns
            if (!getAuthz().isUserHasGraderPermissions(gradebookUid)) {
                viewableAssignments = getAssignments(gradebook.getId(), SortType.SORT_BY_SORTING, true);
            } else {
                // this user has grader perms, so we need to filter the items returned
                // if this gradebook has categories enabled, we need to check for category-specific restrictions

                if (gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_NO_CATEGORY) {
                    assignmentsToReturn.addAll(getAssignments(gradebookUid));
                } else {

                    String userUid = getUserUid();
                    if (getGradebookPermissionService().getPermissionForUserForAllAssignment(gradebook.getId(),
                            userUid)) {
                        assignmentsToReturn.addAll(getAssignments(gradebookUid));
                    }

                    // categories are enabled, so we need to check the category restrictions
                    List allCategories = getCategoriesWithAssignments(gradebook.getId());
                    if (allCategories != null && !allCategories.isEmpty()) {
                        List<Long> catIds = new ArrayList<Long>();
                        for (Category category : (List<Category>) allCategories) {
                            catIds.add(category.getId());
                        }
                        List<Long> viewableCategorieIds = getGradebookPermissionService()
                                .getCategoriesForUser(gradebook.getId(), userUid, catIds);
                        List<Category> viewableCategories = new ArrayList<Category>();
                        for (Category category : (List<Category>) allCategories) {
                            if (viewableCategorieIds.contains(category.getId())) {
                                viewableCategories.add(category);
                            }
                        }

                        for (Iterator catIter = viewableCategories.iterator(); catIter.hasNext();) {
                            Category cat = (Category) catIter.next();
                            if (cat != null) {
                                List assignments = cat.getAssignmentList();
                                if (assignments != null && !assignments.isEmpty()) {
                                    viewableAssignments.addAll(assignments);
                                }
                            }
                        }
                    }
                }
            }
        } else if (getAuthz().isUserAbleToViewOwnGrades(gradebookUid)) {
            // if user is just a student, we need to filter out unreleased items
            List allAssigns = getAssignments(gradebook.getId(), null, true);
            if (allAssigns != null) {
                for (Iterator aIter = allAssigns.iterator(); aIter.hasNext();) {
                    Assignment assign = (Assignment) aIter.next();
                    if (assign != null && assign.isReleased()) {
                        viewableAssignments.add(assign);
                    }
                }
            }
        }

        // Now we need to convert these to the assignment template objects
        if (viewableAssignments != null && !viewableAssignments.isEmpty()) {
            for (Iterator assignIter = viewableAssignments.iterator(); assignIter.hasNext();) {
                Assignment assignment = (Assignment) assignIter.next();
                assignmentsToReturn.add(getAssignmentDefinition(assignment));
            }
        }

        return new ArrayList<>(assignmentsToReturn);

    }

    @Override
    public Map<String, String> getViewableStudentsForItemForCurrentUser(final String gradebookUid,
            final Long gradableObjectId) {
        String userUid = authn.getUserUid();

        return getViewableStudentsForItemForUser(userUid, gradebookUid, gradableObjectId);
    }

    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public Map<String, String> getViewableStudentsForItemForUser(final String userUid, final String gradebookUid,
            final Long gradableObjectId) {

        if (gradebookUid == null || gradableObjectId == null || userUid == null) {
            throw new IllegalArgumentException("null gradebookUid or gradableObjectId or "
                    + "userId passed to getViewableStudentsForUserForItem." + " gradebookUid: " + gradebookUid
                    + " gradableObjectId:" + gradableObjectId + " userId: " + userUid);
        }

        if (!authz.isUserAbleToGrade(gradebookUid, userUid)) {
            return new HashMap<>();
        }

        Assignment gradebookItem = (Assignment) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                return getAssignmentWithoutStats(gradebookUid, gradableObjectId, session);
            }
        });

        if (gradebookItem == null) {
            log.debug("The gradebook item does not exist, so returning empty set");
            return new HashMap();
        }

        Long categoryId = gradebookItem.getCategory() == null ? null : gradebookItem.getCategory().getId();

        Map<EnrollmentRecord, String> enrRecFunctionMap = authz.findMatchingEnrollmentsForItemForUser(userUid,
                gradebookUid, categoryId, getGradebook(gradebookUid).getCategory_type(), null, null);
        if (enrRecFunctionMap == null) {
            return new HashMap();
        }

        Map<String, String> studentIdFunctionMap = new HashMap();
        for (Iterator<Entry<EnrollmentRecord, String>> enrIter = enrRecFunctionMap.entrySet().iterator(); enrIter
                .hasNext();) {
            Entry<EnrollmentRecord, String> entry = enrIter.next();
            EnrollmentRecord enr = entry.getKey();
            if (enr != null && enrRecFunctionMap.get(enr) != null) {
                studentIdFunctionMap.put(enr.getUser().getUserUid(), entry.getValue());
            }
        }
        return studentIdFunctionMap;
    }

    @Override
    public boolean isGradableObjectDefined(Long gradableObjectId) {
        if (gradableObjectId == null) {
            throw new IllegalArgumentException("null gradableObjectId passed to isGradableObjectDefined");
        }

        return isAssignmentDefined(gradableObjectId);
    }

    @Override
    public Map getViewableSectionUuidToNameMap(String gradebookUid) {
        if (gradebookUid == null) {
            throw new IllegalArgumentException("Null gradebookUid passed to getViewableSectionIdToNameMap");
        }

        Map<String, String> sectionIdNameMap = new HashMap<>();

        List viewableCourseSections = getAuthz().getViewableSections(gradebookUid);
        if (viewableCourseSections == null || viewableCourseSections.isEmpty()) {
            return sectionIdNameMap;
        }

        for (Iterator sectionIter = viewableCourseSections.iterator(); sectionIter.hasNext();) {
            CourseSection section = (CourseSection) sectionIter.next();
            if (section != null) {
                sectionIdNameMap.put(section.getUuid(), section.getTitle());
            }
        }

        return sectionIdNameMap;
    }

    @Override
    public boolean currentUserHasGradeAllPerm(String gradebookUid) {
        return authz.isUserAbleToGradeAll(gradebookUid);
    }

    @Override
    public boolean isUserAllowedToGradeAll(String gradebookUid, String userUid) {
        return authz.isUserAbleToGradeAll(gradebookUid, userUid);
    }

    @Override
    public boolean currentUserHasGradingPerm(String gradebookUid) {
        return authz.isUserAbleToGrade(gradebookUid);
    }

    @Override
    public boolean isUserAllowedToGrade(String gradebookUid, String userUid) {
        return authz.isUserAbleToGrade(gradebookUid, userUid);
    }

    @Override
    public boolean currentUserHasEditPerm(String gradebookUid) {
        return authz.isUserAbleToEditAssessments(gradebookUid);
    }

    @Override
    public boolean currentUserHasViewOwnGradesPerm(String gradebookUid) {
        return authz.isUserAbleToViewOwnGrades(gradebookUid);
    }

    @Override
    public List<GradeDefinition> getGradesForStudentsForItem(final String gradebookUid, final Long gradableObjectId,
            List<String> studentIds) {
        if (gradableObjectId == null) {
            throw new IllegalArgumentException("null gradableObjectId passed to getGradesForStudentsForItem");
        }

        List<org.sakaiproject.service.gradebook.shared.GradeDefinition> studentGrades = new ArrayList();

        if (studentIds != null && !studentIds.isEmpty()) {
            // first, we need to make sure the current user is authorized to view the
            // grades for all of the requested students
            Assignment gbItem = (Assignment) getHibernateTemplate().execute(new HibernateCallback() {
                public Object doInHibernate(Session session) throws HibernateException {
                    return getAssignmentWithoutStats(gradebookUid, gradableObjectId, session);
                }
            });

            if (gbItem != null) {
                Gradebook gradebook = gbItem.getGradebook();

                if (!authz.isUserAbleToGrade(gradebook.getUid())) {
                    throw new SecurityException("User " + authn.getUserUid()
                            + " attempted to access grade information without permission in gb "
                            + gradebook.getUid() + " using gradebookService.getGradesForStudentsForItem");
                }

                Long categoryId = gbItem.getCategory() != null ? gbItem.getCategory().getId() : null;
                Map enrRecFunctionMap = authz.findMatchingEnrollmentsForItem(gradebook.getUid(), categoryId,
                        gradebook.getCategory_type(), null, null);
                Set enrRecs = enrRecFunctionMap.keySet();
                Map studentIdEnrRecMap = new HashMap();
                if (enrRecs != null) {
                    for (Iterator enrIter = enrRecs.iterator(); enrIter.hasNext();) {
                        EnrollmentRecord enr = (EnrollmentRecord) enrIter.next();
                        if (enr != null) {
                            studentIdEnrRecMap.put(enr.getUser().getUserUid(), enr);
                        }
                    }
                }

                for (Iterator stIter = studentIds.iterator(); stIter.hasNext();) {
                    String studentId = (String) stIter.next();
                    if (studentId != null) {
                        if (!studentIdEnrRecMap.containsKey(studentId)) {
                            throw new SecurityException("User " + authn.getUserUid()
                                    + " attempted to access grade information for student " + studentId
                                    + " without permission in gb " + gradebook.getUid()
                                    + " using gradebookService.getGradesForStudentsForItem");
                        }
                    }
                }

                // retrieve the grading comments for all of the students
                List<Comment> commentRecs = getComments(gbItem, studentIds);
                Map<String, String> studentIdCommentTextMap = new HashMap();
                if (commentRecs != null) {
                    for (Iterator<Comment> cIter = commentRecs.iterator(); cIter.hasNext();) {
                        Comment comment = cIter.next();
                        if (comment != null) {
                            studentIdCommentTextMap.put(comment.getStudentId(), comment.getCommentText());
                        }
                    }
                }

                // now, we can populate the grade information
                List<AssignmentGradeRecord> gradeRecs = getAllAssignmentGradeRecordsForGbItem(gradableObjectId,
                        studentIds);
                if (gradeRecs != null) {
                    if (gradebook.getGrade_type() == GradebookService.GRADE_TYPE_LETTER) {
                        convertPointsToLetterGrade(gradebook, gradeRecs);
                    } else if (gradebook.getGrade_type() == GradebookService.GRADE_TYPE_PERCENTAGE) {
                        convertPointsToPercentage(gradebook, gradeRecs);
                    }

                    for (Iterator gradeIter = gradeRecs.iterator(); gradeIter.hasNext();) {
                        AssignmentGradeRecord agr = (AssignmentGradeRecord) gradeIter.next();
                        if (agr != null) {
                            String commentText = studentIdCommentTextMap.get(agr.getStudentId());
                            GradeDefinition gradeDef = convertGradeRecordToGradeDefinition(agr, gbItem, gradebook,
                                    commentText);

                            studentGrades.add(gradeDef);
                        }
                    }
                }
            }
        }

        return studentGrades;
    }

    @Override
    public Map<Long, List<GradeDefinition>> getGradesWithoutCommentsForStudentsForItems(final String gradebookUid,
            final List<Long> gradableObjectIds, List<String> studentIds) {
        if (!authz.isUserAbleToGrade(gradebookUid)) {
            throw new SecurityException("You do not have permission to perform this operation");
        }

        if (gradableObjectIds == null || gradableObjectIds.isEmpty()) {
            throw new IllegalArgumentException(
                    "null or empty gradableObjectIds passed to getGradesWithoutCommentsForStudentsForItems");
        }

        Map<Long, List<GradeDefinition>> gradesMap = new HashMap<Long, List<GradeDefinition>>();
        if (studentIds == null || studentIds.isEmpty()) {
            // We could populate the map with (gboId : new ArrayList()), but it's cheaper to allow get(gboId) to return null.
            return gradesMap;
        }

        // Get all the grades for the gradableObjectIds
        List<AssignmentGradeRecord> gradeRecords = getAllAssignmentGradeRecordsForGbItems(gradableObjectIds,
                studentIds);
        // AssignmentGradeRecord is not in the API. So we need to convert grade records into GradeDefinition objects.
        // GradeDefinitions are not tied to their gbos, so we need to return a map associating them back to their gbos
        List<GradeDefinition> gradeDefinitions = new ArrayList<GradeDefinition>();
        for (AssignmentGradeRecord gradeRecord : gradeRecords) {
            Assignment gbo = (Assignment) gradeRecord.getGradableObject();
            Long gboId = gbo.getId();
            Gradebook gradebook = gbo.getGradebook();
            if (!gradebookUid.equals(gradebook.getUid())) {
                // The user is authorized against gradebookUid, but we have grades for another gradebook.
                // This is an authorization issue caused by gradableObjectIds violating the method contract.
                throw new IllegalArgumentException("gradableObjectIds must belong to grades within this gradebook");
            }

            GradeDefinition gradeDef = convertGradeRecordToGradeDefinition(gradeRecord, gbo, gradebook, null);

            List<GradeDefinition> gradeList = gradesMap.get(gboId);
            if (gradeList == null) {
                gradeList = new ArrayList<GradeDefinition>();
                gradesMap.put(gboId, gradeList);
            }
            gradeList.add(gradeDef);
        }

        return gradesMap;
    }

    /**
     * Converts an AssignmentGradeRecord into a GradeDefinition object.
     * @param gradeRecord
     * @param gbo
     * @param gradebook
     * @param commentText - goes into the GradeComment attribute. Will be omitted if null
     * @return a GradeDefinition object whose attributes match the passed in gradeRecord
     */
    private GradeDefinition convertGradeRecordToGradeDefinition(AssignmentGradeRecord gradeRecord, Assignment gbo,
            Gradebook gradebook, String commentText) {
        GradeDefinition gradeDef = new GradeDefinition();
        gradeDef.setStudentUid(gradeRecord.getStudentId());
        gradeDef.setGraderUid(gradeRecord.getGraderId());
        gradeDef.setDateRecorded(gradeRecord.getDateRecorded());
        int gradeEntryType = gradebook.getGrade_type();
        gradeDef.setGradeEntryType(gradeEntryType);
        String grade = null;
        if (gradeEntryType == GradebookService.GRADE_TYPE_LETTER) {
            grade = gradeRecord.getLetterEarned();
        } else if (gradeEntryType == GradebookService.GRADE_TYPE_PERCENTAGE) {
            Double percentEarned = gradeRecord.getPercentEarned();
            grade = percentEarned != null ? percentEarned.toString() : null;
        } else {
            Double pointsEarned = gradeRecord.getPointsEarned();
            grade = pointsEarned != null ? pointsEarned.toString() : null;
        }
        gradeDef.setGrade(grade);
        gradeDef.setGradeReleased(gradebook.isAssignmentsDisplayed() && gbo.isReleased());

        if (commentText != null) {
            gradeDef.setGradeComment(commentText);
        }

        return gradeDef;
    }

    @Override
    public boolean isGradeValid(String gradebookUuid, String grade) {
        if (gradebookUuid == null) {
            throw new IllegalArgumentException("Null gradebookUuid passed to isGradeValid");
        }
        Gradebook gradebook;
        try {
            gradebook = getGradebook(gradebookUuid);
        } catch (GradebookNotFoundException gnfe) {
            throw new GradebookNotFoundException("No gradebook exists with the given gradebookUid: " + gradebookUuid
                    + "Error: " + gnfe.getMessage());
        }

        int gradeEntryType = gradebook.getGrade_type();
        LetterGradePercentMapping mapping = null;
        if (gradeEntryType == GradebookService.GRADE_TYPE_LETTER) {
            mapping = getLetterGradePercentMapping(gradebook);
        }

        return isGradeValid(grade, gradeEntryType, mapping);
    }

    private boolean isGradeValid(String grade, int gradeEntryType, LetterGradePercentMapping gradeMapping) {

        boolean gradeIsValid = false;

        if (grade == null || "".equals(grade)) {

            gradeIsValid = true;

        } else {

            if (gradeEntryType == GradebookService.GRADE_TYPE_POINTS
                    || gradeEntryType == GradebookService.GRADE_TYPE_PERCENTAGE) {
                try {
                    Double gradeAsDouble = Double.parseDouble(grade);
                    // grade must be greater than or equal to 0
                    if (gradeAsDouble.doubleValue() >= 0) {
                        // check that there are no more than 2 decimal places
                        String[] splitOnDecimal = grade.split("\\.");
                        if (splitOnDecimal == null || splitOnDecimal.length < 2) {
                            gradeIsValid = true;
                        } else if (splitOnDecimal.length == 2) {
                            String decimal = splitOnDecimal[1];
                            if (decimal.length() <= 2) {
                                gradeIsValid = true;
                            }
                        }
                    }
                } catch (NumberFormatException nfe) {
                    if (log.isDebugEnabled())
                        log.debug("Passed grade is not a numeric value");
                }

            } else if (gradeEntryType == GradebookService.GRADE_TYPE_LETTER) {
                if (gradeMapping == null) {
                    throw new IllegalArgumentException(
                            "Null mapping passed to isGradeValid for a letter grade-based gradeook");
                }

                String standardizedGrade = gradeMapping.standardizeInputGrade(grade);
                if (standardizedGrade != null) {
                    gradeIsValid = true;
                }
            } else {
                throw new IllegalArgumentException("Invalid gradeEntryType passed to isGradeValid");
            }
        }

        return gradeIsValid;
    }

    @Override
    public List<String> identifyStudentsWithInvalidGrades(String gradebookUid,
            Map<String, String> studentIdToGradeMap) {
        if (gradebookUid == null) {
            throw new IllegalArgumentException("null gradebookUid passed to identifyStudentsWithInvalidGrades");
        }

        List<String> studentsWithInvalidGrade = new ArrayList<String>();

        if (studentIdToGradeMap != null) {
            Gradebook gradebook;

            try {
                gradebook = getGradebook(gradebookUid);
            } catch (GradebookNotFoundException gnfe) {
                throw new GradebookNotFoundException("No gradebook exists with the given gradebookUid: "
                        + gradebookUid + "Error: " + gnfe.getMessage());
            }

            LetterGradePercentMapping gradeMapping = null;
            if (gradebook.getGrade_type() == GradebookService.GRADE_TYPE_LETTER) {
                gradeMapping = getLetterGradePercentMapping(gradebook);
            }

            for (String studentId : studentIdToGradeMap.keySet()) {
                String grade = studentIdToGradeMap.get(studentId);
                if (!isGradeValid(grade, gradebook.getGrade_type(), gradeMapping)) {
                    studentsWithInvalidGrade.add(studentId);
                }
            }
        }
        return studentsWithInvalidGrade;
    }

    @Override
    public void saveGradeAndCommentForStudent(String gradebookUid, Long gradableObjectId, String studentUid,
            String grade, String comment) {
        if (gradebookUid == null || gradableObjectId == null || studentUid == null) {
            throw new IllegalArgumentException(
                    "Null gradebookUid or gradableObjectId or studentUid passed to saveGradeAndCommentForStudent");
        }

        GradeDefinition gradeDef = new GradeDefinition();
        gradeDef.setStudentUid(studentUid);
        gradeDef.setGrade(grade);
        gradeDef.setGradeComment(comment);

        List<GradeDefinition> gradeDefList = new ArrayList<GradeDefinition>();
        gradeDefList.add(gradeDef);

        saveGradesAndComments(gradebookUid, gradableObjectId, gradeDefList);
    }

    @Override
    public void saveGradesAndComments(final String gradebookUid, final Long gradableObjectId,
            List<GradeDefinition> gradeDefList) {
        if (gradebookUid == null || gradableObjectId == null) {
            throw new IllegalArgumentException(
                    "Null gradebookUid or gradableObjectId passed to saveGradesAndComments");
        }

        if (gradeDefList != null) {
            Gradebook gradebook;

            try {
                gradebook = getGradebook(gradebookUid);
            } catch (GradebookNotFoundException gnfe) {
                throw new GradebookNotFoundException("No gradebook exists with the given gradebookUid: "
                        + gradebookUid + "Error: " + gnfe.getMessage());
            }

            Assignment assignment = (Assignment) getHibernateTemplate().execute(new HibernateCallback() {
                public Object doInHibernate(Session session) throws HibernateException {
                    return getAssignmentWithoutStats(gradebookUid, gradableObjectId, session);
                }
            });

            if (assignment == null) {
                throw new AssessmentNotFoundException(
                        "No gradebook item exists with gradable object id = " + gradableObjectId);
            }

            if (!currentUserHasGradingPerm(gradebookUid)) {
                log.warn("User attempted to save grades and comments without authorization");
                throw new SecurityException(
                        "Current user is not authorized to save grades or comments in gradebook " + gradebookUid);
            }

            // let's identify all of the students being updated first
            Map<String, GradeDefinition> studentIdGradeDefMap = new HashMap<String, GradeDefinition>();
            Map<String, String> studentIdToGradeMap = new HashMap<String, String>();

            for (GradeDefinition gradeDef : gradeDefList) {
                studentIdGradeDefMap.put(gradeDef.getStudentUid(), gradeDef);
                studentIdToGradeMap.put(gradeDef.getStudentUid(), gradeDef.getGrade());
            }

            // check for invalid grades
            List invalidStudents = identifyStudentsWithInvalidGrades(gradebookUid, studentIdToGradeMap);
            if (invalidStudents != null && !invalidStudents.isEmpty()) {
                throw new InvalidGradeException("At least one grade passed to be updated is "
                        + "invalid. No grades or comments were updated.");
            }

            boolean userHasGradeAllPerm = currentUserHasGradeAllPerm(gradebookUid);

            // let's retrieve all of the existing grade recs for the given students
            // and assignments
            List<AssignmentGradeRecord> allGradeRecs = getAllAssignmentGradeRecordsForGbItem(gradableObjectId,
                    studentIdGradeDefMap.keySet());

            // put in map for easier accessibility
            Map<String, AssignmentGradeRecord> studentIdToAgrMap = new HashMap<String, AssignmentGradeRecord>();
            if (allGradeRecs != null) {
                for (AssignmentGradeRecord rec : allGradeRecs) {
                    studentIdToAgrMap.put(rec.getStudentId(), rec);
                }
            }

            // set up the grader and grade time
            String graderId = getAuthn().getUserUid();
            Date now = new Date();

            // get grade mapping, if nec, to convert grades to points
            LetterGradePercentMapping mapping = null;
            if (gradebook.getGrade_type() == GradebookService.GRADE_TYPE_LETTER) {
                mapping = getLetterGradePercentMapping(gradebook);
            }

            // get all of the comments, as well
            List<Comment> allComments = getComments(assignment, studentIdGradeDefMap.keySet());
            // put in a map for easier accessibility
            Map<String, Comment> studentIdCommentMap = new HashMap<String, Comment>();
            if (allComments != null) {
                for (Comment comment : allComments) {
                    studentIdCommentMap.put(comment.getStudentId(), comment);
                }
            }

            // these are the records that will need to be updated. iterate through
            // everything and then we'll save it all at once
            Set<AssignmentGradeRecord> agrToUpdate = new HashSet<AssignmentGradeRecord>();
            // do not use a HashSet b/c you may have multiple Comments with null id and the same comment at this point.
            // the Comment object defines objects as equal if they have the same id, comment text, and gb item. the
            // only difference may be the student ids
            List<Comment> commentsToUpdate = new ArrayList<Comment>();
            Set<GradingEvent> eventsToAdd = new HashSet<GradingEvent>();

            for (GradeDefinition gradeDef : gradeDefList) {

                String studentId = gradeDef.getStudentUid();

                // check specific grading privileges if user does not have
                // grade all perm
                if (!userHasGradeAllPerm) {
                    if (!isUserAbleToGradeItemForStudent(gradebookUid, gradableObjectId, studentId)) {
                        log.warn("User " + graderId + " attempted to save a grade for " + studentId
                                + " without authorization");

                        throw new SecurityException("User " + graderId + " attempted to save a grade for "
                                + studentId + " without authorization");
                    }
                }

                Double convertedGrade = convertInputGradeToPoints(gradebook.getGrade_type(), mapping,
                        assignment.getPointsPossible(), gradeDef.getGrade());

                // let's see if this agr needs to be updated
                AssignmentGradeRecord gradeRec = studentIdToAgrMap.get(studentId);
                if (gradeRec != null) {
                    if ((convertedGrade == null && gradeRec.getPointsEarned() != null)
                            || (convertedGrade != null && gradeRec.getPointsEarned() == null)
                            || (convertedGrade != null && gradeRec.getPointsEarned() != null
                                    && !convertedGrade.equals(gradeRec.getPointsEarned()))) {

                        gradeRec.setPointsEarned(convertedGrade);
                        gradeRec.setGraderId(graderId);
                        gradeRec.setDateRecorded(now);

                        agrToUpdate.add(gradeRec);

                        // we also need to add a GradingEvent
                        // the event stores the actual input grade, not the converted one
                        GradingEvent event = new GradingEvent(assignment, graderId, studentId, gradeDef.getGrade());
                        eventsToAdd.add(event);
                    }
                } else {
                    // if the grade is something other than null, add a new AGR
                    if (gradeDef.getGrade() != null && !gradeDef.getGrade().trim().equals("")) {
                        gradeRec = new AssignmentGradeRecord(assignment, studentId, convertedGrade);
                        gradeRec.setPointsEarned(convertedGrade);
                        gradeRec.setGraderId(graderId);
                        gradeRec.setDateRecorded(now);

                        agrToUpdate.add(gradeRec);

                        // we also need to add a GradingEvent
                        // the event stores the actual input grade, not the converted one
                        GradingEvent event = new GradingEvent(assignment, graderId, studentId, gradeDef.getGrade());
                        eventsToAdd.add(event);
                    }
                }

                // let's see if the comment needs to be updated
                Comment comment = studentIdCommentMap.get(studentId);
                if (comment != null) {
                    boolean oldCommentIsNull = comment.getCommentText() == null
                            || comment.getCommentText().equals("");
                    boolean newCommentIsNull = gradeDef.getGradeComment() == null
                            || gradeDef.getGradeComment().equals("");

                    if ((oldCommentIsNull && !newCommentIsNull) || (!oldCommentIsNull && newCommentIsNull)
                            || (!oldCommentIsNull && !newCommentIsNull
                                    && !gradeDef.getGradeComment().equals(comment.getCommentText()))) {
                        // update this comment
                        comment.setCommentText(gradeDef.getGradeComment());
                        comment.setGraderId(graderId);
                        comment.setDateRecorded(now);

                        commentsToUpdate.add(comment);
                    }
                } else {
                    // if there is a comment, add it
                    if (gradeDef.getGradeComment() != null && !gradeDef.getGradeComment().trim().equals("")) {
                        comment = new Comment(studentId, gradeDef.getGradeComment(), assignment);
                        comment.setGraderId(graderId);
                        comment.setDateRecorded(now);

                        commentsToUpdate.add(comment);
                    }
                }
            }

            // now let's save them
            try {
                for (AssignmentGradeRecord assignmentGradeRecord : agrToUpdate) {
                    getHibernateTemplate().saveOrUpdate(assignmentGradeRecord);
                }
                for (Comment comment : commentsToUpdate) {
                    getHibernateTemplate().saveOrUpdate(comment);
                }
                for (GradingEvent gradingEvent : eventsToAdd) {
                    getHibernateTemplate().saveOrUpdate(gradingEvent);
                }
            } catch (HibernateOptimisticLockingFailureException holfe) {
                if (log.isInfoEnabled())
                    log.info(
                            "An optimistic locking failure occurred while attempting to save scores and comments for gb Item "
                                    + gradableObjectId);
                throw new StaleObjectModificationException(holfe);
            } catch (StaleObjectStateException sose) {
                if (log.isInfoEnabled())
                    log.info(
                            "An optimistic locking failure occurred while attempting to save scores and comments for gb Item "
                                    + gradableObjectId);
                throw new StaleObjectModificationException(sose);
            }
        }
    }

    /**
     * 
     * @param gradeEntryType
     * @param mapping
     * @param gbItemPointsPossible
     * @param grade
     * @return given a generic String grade, converts it to the equivalent Double
     * point value that will be stored in the db based upon the gradebook's grade entry type
     */
    private Double convertInputGradeToPoints(int gradeEntryType, LetterGradePercentMapping mapping,
            Double gbItemPointsPossible, String grade) throws InvalidGradeException {
        Double convertedValue = null;

        if (grade != null && !"".equals(grade)) {
            if (gradeEntryType == GradebookService.GRADE_TYPE_POINTS) {
                try {
                    Double pointValue = Double.parseDouble(grade);
                    convertedValue = pointValue;
                } catch (NumberFormatException nfe) {
                    throw new InvalidGradeException("Invalid grade passed to convertInputGradeToPoints");
                }
            } else if (gradeEntryType == GradebookService.GRADE_TYPE_PERCENTAGE
                    || gradeEntryType == GradebookService.GRADE_TYPE_LETTER) {

                // for letter or %-based grading, we need to calculate the equivalent point value
                if (gbItemPointsPossible == null) {
                    throw new IllegalArgumentException("Null points possible passed"
                            + " to convertInputGradeToPoints for letter or % based grading");
                }

                Double percentage = null;
                if (gradeEntryType == GradebookService.GRADE_TYPE_LETTER) {
                    if (mapping == null) {
                        throw new IllegalArgumentException(
                                "No mapping passed to convertInputGradeToPoints for a letter-based gb");
                    }

                    if (mapping.getGradeMap() != null) {
                        // standardize the grade mapping
                        String standardizedGrade = mapping.standardizeInputGrade(grade);
                        percentage = mapping.getValue(standardizedGrade);
                        if (percentage == null) {
                            throw new IllegalArgumentException("Invalid grade passed to convertInputGradeToPoints");
                        }
                    }
                } else {
                    try {
                        percentage = Double.parseDouble(grade);
                    } catch (NumberFormatException nfe) {
                        throw new IllegalArgumentException("Invalid % grade passed to convertInputGradeToPoints");
                    }
                }

                convertedValue = calculateEquivalentPointValueForPercent(gbItemPointsPossible, percentage);

            } else {
                throw new InvalidGradeException("invalid grade entry type passed to convertInputGradeToPoints");
            }
        }

        return convertedValue;
    }

    @Override
    public int getGradeEntryType(String gradebookUid) {
        if (gradebookUid == null) {
            throw new IllegalArgumentException("null gradebookUid passed to getGradeEntryType");
        }

        try {
            Gradebook gradebook = getGradebook(gradebookUid);
            return gradebook.getGrade_type();
        } catch (GradebookNotFoundException gnfe) {
            throw new GradebookNotFoundException(
                    "No gradebook exists with the given gradebookUid: " + gradebookUid);
        }
    }

    @Override
    public Map getEnteredCourseGrade(final String gradebookUid) {
        HibernateCallback hc = new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Gradebook thisGradebook = getGradebook(gradebookUid);

                Long gradebookId = thisGradebook.getId();
                CourseGrade courseGrade = getCourseGrade(gradebookId);

                Map enrollmentMap;

                Map viewableEnrollmentsMap = authz.findMatchingEnrollmentsForViewableCourseGrade(gradebookUid,
                        thisGradebook.getCategory_type(), null, null);
                enrollmentMap = new HashMap();

                Map enrollmentMapUid = new HashMap();
                for (Iterator iter = viewableEnrollmentsMap.keySet().iterator(); iter.hasNext();) {
                    EnrollmentRecord enr = (EnrollmentRecord) iter.next();
                    enrollmentMap.put(enr.getUser().getUserUid(), enr);
                    enrollmentMapUid.put(enr.getUser().getUserUid(), enr);
                }

                Query q = session
                        .createQuery("from CourseGradeRecord as cgr where cgr.gradableObject.id=:gradableObjectId");
                q.setLong("gradableObjectId", courseGrade.getId().longValue());
                List records = filterAndPopulateCourseGradeRecordsByStudents(courseGrade, q.list(),
                        enrollmentMap.keySet());

                Map returnMap = new HashMap();

                for (int i = 0; i < records.size(); i++) {
                    CourseGradeRecord cgr = (CourseGradeRecord) records.get(i);
                    if (cgr.getEnteredGrade() != null && !cgr.getEnteredGrade().equalsIgnoreCase("")) {
                        EnrollmentRecord enr = (EnrollmentRecord) enrollmentMapUid.get(cgr.getStudentId());
                        if (enr != null) {
                            returnMap.put(enr.getUser().getDisplayId(), cgr.getEnteredGrade());
                        }
                    }
                }

                return returnMap;
            }
        };
        return (Map) getHibernateTemplate().execute(hc);
    }

    @Override
    public String getAssignmentScoreString(final String gradebookUid, final Long assignmentId,
            final String studentUid) throws GradebookNotFoundException, AssessmentNotFoundException {
        final boolean studentRequestingOwnScore = authn.getUserUid().equals(studentUid);

        if (gradebookUid == null || assignmentId == null || studentUid == null) {
            throw new IllegalArgumentException("null parameter passed to getAssignmentScoreString");
        }

        Double assignmentScore = (Double) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Assignment assignment = getAssignmentWithoutStats(gradebookUid, assignmentId, session);
                if (assignment == null) {
                    throw new AssessmentNotFoundException(
                            "There is no assignment with id " + assignmentId + " in gradebook " + gradebookUid);
                }

                if (!studentRequestingOwnScore
                        && !isUserAbleToViewItemForStudent(gradebookUid, assignmentId, studentUid)) {
                    log.error("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                            + " attempted to retrieve grade for student " + studentUid + " for assignment "
                            + assignment.getName());
                    throw new SecurityException("You do not have permission to perform this operation");
                }

                // If this is the student, then the assignment needs to have
                // been released.
                if (studentRequestingOwnScore && !assignment.isReleased()) {
                    log.error("AUTHORIZATION FAILURE: Student " + getUserUid() + " in gradebook " + gradebookUid
                            + " attempted to retrieve score for unreleased assignment " + assignment.getName());
                    throw new SecurityException("You do not have permission to perform this operation");
                }

                AssignmentGradeRecord gradeRecord = getAssignmentGradeRecord(assignment, studentUid, session);
                if (log.isDebugEnabled())
                    log.debug("gradeRecord=" + gradeRecord);
                if (gradeRecord == null) {
                    return null;
                } else {
                    return gradeRecord.getPointsEarned();
                }
            }
        });
        if (log.isDebugEnabled())
            log.debug("returning " + assignmentScore);

        //TODO: when ungraded items is considered, change column to ungraded-grade 
        //its possible that the assignment score is null
        if (assignmentScore == null)
            return null;

        return Double.valueOf(assignmentScore).toString();
    }

    @Override
    public String getAssignmentScoreString(final String gradebookUid, final String assignmentName,
            final String studentUid) throws GradebookNotFoundException, AssessmentNotFoundException {

        if (gradebookUid == null || assignmentName == null || studentUid == null) {
            throw new IllegalArgumentException("null parameter passed to getAssignmentScoreString");
        }

        Assignment assignment = (Assignment) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                return getAssignmentWithoutStats(gradebookUid, assignmentName, session);
            }
        });

        if (assignment == null) {
            throw new AssessmentNotFoundException(
                    "There is no assignment with name " + assignmentName + " in gradebook " + gradebookUid);
        }

        return getAssignmentScoreString(gradebookUid, assignment.getId(), studentUid);
    }

    @Override
    public void setAssignmentScoreString(final String gradebookUid, final Long assignmentId,
            final String studentUid, final String score, final String clientServiceDescription)
            throws GradebookNotFoundException, AssessmentNotFoundException {
        getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                Assignment assignment = getAssignmentWithoutStats(gradebookUid, assignmentId, session);
                if (assignment == null) {
                    throw new AssessmentNotFoundException(
                            "There is no assignment with id " + assignmentId + " in gradebook " + gradebookUid);
                }
                if (assignment.isExternallyMaintained()) {
                    log.error("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                            + " attempted to grade externally maintained assignment " + assignmentId + " from "
                            + clientServiceDescription);
                    throw new SecurityException("You do not have permission to perform this operation");
                }

                if (!isUserAbleToGradeItemForStudent(gradebookUid, assignment.getId(), studentUid)) {
                    log.error("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                            + " attempted to grade student " + studentUid + " from " + clientServiceDescription
                            + " for item " + assignmentId);
                    throw new SecurityException("You do not have permission to perform this operation");
                }

                Date now = new Date();
                String graderId = getAuthn().getUserUid();
                AssignmentGradeRecord gradeRecord = getAssignmentGradeRecord(assignment, studentUid, session);
                if (gradeRecord == null) {
                    // Creating a new grade record.
                    gradeRecord = new AssignmentGradeRecord(assignment, studentUid, convertStringToDouble(score));
                    //TODO: test if it's ungraded item or not. if yes, set ungraded grade for this record. if not, need validation??
                } else {
                    //TODO: test if it's ungraded item or not. if yes, set ungraded grade for this record. if not, need validation??
                    gradeRecord.setPointsEarned(convertStringToDouble(score));
                }
                gradeRecord.setGraderId(graderId);
                gradeRecord.setDateRecorded(now);
                session.saveOrUpdate(gradeRecord);

                session.save(new GradingEvent(assignment, graderId, studentUid, score));

                // Sync database.
                session.flush();
                session.clear();

                // Post an event in SAKAI_EVENT table
                postUpdateGradeEvent(gradebookUid, assignment.getName(), studentUid, convertStringToDouble(score));
                return null;
            }
        });

        if (log.isInfoEnabled())
            log.info("Score updated in gradebookUid=" + gradebookUid + ", assignmentId=" + assignmentId
                    + " by userUid=" + getUserUid() + " from client=" + clientServiceDescription + ", new score="
                    + score);
    }

    @Override
    public void setAssignmentScoreString(final String gradebookUid, final String assignmentName,
            final String studentUid, final String score, final String clientServiceDescription)
            throws GradebookNotFoundException, AssessmentNotFoundException {

        Assignment assignment = (Assignment) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                return getAssignmentWithoutStats(gradebookUid, assignmentName, session);
            }
        });

        if (assignment == null) {
            throw new AssessmentNotFoundException(
                    "There is no assignment with name " + assignmentName + " in gradebook " + gradebookUid);
        }

        setAssignmentScoreString(gradebookUid, assignment.getId(), studentUid, score, clientServiceDescription);
    }

    @Override
    public void finalizeGrades(String gradebookUid) throws GradebookNotFoundException {
        if (!getAuthz().isUserAbleToGradeAll(gradebookUid)) {
            log.error("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                    + " attempted to finalize grades");
            throw new SecurityException("You do not have permission to perform this operation");
        }
        finalizeNullGradeRecords(getGradebook(gradebookUid));
    }

    @Override
    public String getLowestPossibleGradeForGbItem(final String gradebookUid, final Long gradebookItemId) {
        if (gradebookUid == null || gradebookItemId == null) {
            throw new IllegalArgumentException("Null gradebookUid and/or gradebookItemId "
                    + "passed to getLowestPossibleGradeForGbItem. gradebookUid:" + gradebookUid
                    + " gradebookItemId:" + gradebookItemId);
        }

        Assignment gbItem = (Assignment) getHibernateTemplate().execute(new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException {
                return getAssignmentWithoutStats(gradebookUid, gradebookItemId, session);
            }
        });

        if (gbItem == null) {
            throw new AssessmentNotFoundException("No gradebook item found with id " + gradebookItemId);
        }

        Gradebook gradebook = gbItem.getGradebook();

        // double check that user has some permission to access gb items in this site
        if (!isUserAbleToViewAssignments(gradebookUid) && !currentUserHasViewOwnGradesPerm(gradebookUid)) {
            throw new SecurityException("User attempted to access gradebookItem: " + gradebookItemId
                    + " in gradebook:" + gradebookUid + " without permission!");
        }

        String lowestPossibleGrade = null;

        if (gbItem.getUngraded()) {
            lowestPossibleGrade = null;
        } else if (gradebook.getGrade_type() == GradebookService.GRADE_TYPE_PERCENTAGE
                || gradebook.getGrade_type() == GradebookService.GRADE_TYPE_POINTS) {
            lowestPossibleGrade = "0";
        } else if (gbItem.getGradebook().getGrade_type() == GradebookService.GRADE_TYPE_LETTER) {
            LetterGradePercentMapping mapping = getLetterGradePercentMapping(gradebook);
            lowestPossibleGrade = mapping.getGrade(0d);
        }

        return lowestPossibleGrade;
    }

    @Override
    public List<CategoryDefinition> getCategoryDefinitions(String gradebookUid) {
        if (gradebookUid == null) {
            throw new IllegalArgumentException("Null gradebookUid passed to getCategoryDefinitions");
        }

        if (!isUserAbleToViewAssignments(gradebookUid)) {
            log.warn("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                    + " attempted to retrieve all categories without permission");
            throw new SecurityException("You do not have permission to perform this operation");
        }

        List<CategoryDefinition> categoryDefList = new ArrayList<CategoryDefinition>();

        List<Category> gbCategories = getCategories(getGradebook(gradebookUid).getId());
        if (gbCategories != null) {
            for (Category category : gbCategories) {
                categoryDefList.add(getCategoryDefinition(category));
            }
        }

        return categoryDefList;
    }

    private CategoryDefinition getCategoryDefinition(Category category) {
        CategoryDefinition categoryDef = new CategoryDefinition();
        if (category != null) {
            categoryDef.setId(category.getId());
            categoryDef.setName(category.getName());
            categoryDef.setWeight(category.getWeight());
            categoryDef.setDrop_lowest(category.getDrop_lowest());
            categoryDef.setDropHighest(category.getDropHighest());
            categoryDef.setKeepHighest(category.getKeepHighest());
            categoryDef.setAssignmentList(getAssignments(category.getGradebook().getUid(), category.getName()));
            categoryDef.setExtraCredit(category.isExtraCredit());
        }

        return categoryDef;
    }

    /**
     * 
     * @param session
     * @param gradebookId
     * @param studentUids
     * @return a map of studentUid to a list of that student's AssignmentGradeRecords for the given studentUids list
     * in the given gradebook.  the grade records are all recs for assignments that are not removed and
     * have a points possible > 0
     */
    protected Map<String, List<AssignmentGradeRecord>> getGradeRecordMapForStudents(Session session,
            Long gradebookId, Collection<String> studentUids) {
        Map<String, List<AssignmentGradeRecord>> filteredGradeRecs = new HashMap<String, List<AssignmentGradeRecord>>();
        if (studentUids != null) {
            List<AssignmentGradeRecord> allGradeRecs = new ArrayList<AssignmentGradeRecord>();

            if (studentUids.size() >= MAX_NUMBER_OF_SQL_PARAMETERS_IN_LIST) {
                allGradeRecs = session
                        .createQuery("from AssignmentGradeRecord agr where agr.gradableObject.gradebook.id=:gbid "
                                + "and agr.gradableObject.removed=false")
                        .setParameter("gbid", gradebookId).list();
            } else {
                String query = "from AssignmentGradeRecord agr where agr.gradableObject.gradebook.id=:gbid and "
                        + "agr.gradableObject.removed=false and " + "agr.studentId in (:studentUids)";

                allGradeRecs = session.createQuery(query).setParameter("gbid", gradebookId)
                        .setParameterList("studentUids", studentUids).list();
            }

            if (allGradeRecs != null) {
                for (AssignmentGradeRecord gradeRec : allGradeRecs) {
                    if (studentUids.contains(gradeRec.getStudentId())) {
                        String studentId = gradeRec.getStudentId();
                        List<AssignmentGradeRecord> gradeRecList = filteredGradeRecs.get(studentId);
                        if (gradeRecList == null) {
                            gradeRecList = new ArrayList<AssignmentGradeRecord>();
                            gradeRecList.add(gradeRec);
                            filteredGradeRecs.put(studentId, gradeRecList);
                        } else {
                            gradeRecList.add(gradeRec);
                            filteredGradeRecs.put(studentId, gradeRecList);
                        }
                    }
                }
            }
        }

        return filteredGradeRecs;
    }

    /**
     * 
     * @param session
     * @param gradebookId
     * @return a list of Assignments that have not been removed, are "counted", graded,
     * and have a points possible > 0
     */
    protected List<Assignment> getCountedAssignments(Session session, Long gradebookId) {
        List<Assignment> assignList = new ArrayList<Assignment>();

        List<Assignment> results = session
                .createQuery("from Assignment as asn where asn.gradebook.id=:gbid and asn.removed=false and "
                        + "asn.notCounted=false and asn.ungraded=false")
                .setParameter("gbid", gradebookId).list();

        if (results != null) {
            // making sure there's no invalid points possible for normal assignments
            for (Assignment a : results) {

                if (a.getPointsPossible() != null && a.getPointsPossible() > 0) {
                    assignList.add(a);
                }
            }
        }

        return assignList;
    }

    /**
      * set the droppedFromGrade attribute of each 
      * of the n highest and the n lowest scores of a 
      * student based on the assignment's category
      * @param gradeRecords
      * @return void
      * 
      * NOTE: When the UI changes, this needs to be made private again
      */
    public void applyDropScores(Collection<AssignmentGradeRecord> gradeRecords) {
        if (gradeRecords == null || gradeRecords.size() < 1) {
            return;
        }
        long start = System.currentTimeMillis();

        List<String> studentIds = new ArrayList<String>();
        List<Category> categories = new ArrayList<Category>();
        Map<String, List<AssignmentGradeRecord>> gradeRecordMap = new HashMap<String, List<AssignmentGradeRecord>>();
        for (AssignmentGradeRecord gradeRecord : gradeRecords) {

            if (gradeRecord == null || gradeRecord.getPointsEarned() == null) { // don't consider grades that have null pointsEarned (this occurs when a previously entered score for an assignment is removed; record stays in database) 
                continue;
            }

            // reset
            gradeRecord.setDroppedFromGrade(false);

            Assignment assignment = gradeRecord.getAssignment();
            if (assignment.getUngraded() // GradebookService.GRADE_TYPE_LETTER
                    || assignment.isNotCounted() // don't consider grades that are not counted toward course grade
                    || assignment.getItemType().equals(Assignment.item_type_adjustment) || assignment.isRemoved()) {
                continue;
            }
            // get all the students represented
            String studentId = gradeRecord.getStudentId();
            if (!studentIds.contains(studentId)) {
                studentIds.add(studentId);
            }
            // get all the categories represented
            Category cat = gradeRecord.getAssignment().getCategory();
            if (cat != null) {
                if (!categories.contains(cat)) {
                    categories.add(cat);
                }
                List<AssignmentGradeRecord> gradeRecordsByCatAndStudent = gradeRecordMap
                        .get(studentId + cat.getId());
                if (gradeRecordsByCatAndStudent == null) {
                    gradeRecordsByCatAndStudent = new ArrayList<AssignmentGradeRecord>();
                    gradeRecordsByCatAndStudent.add(gradeRecord);
                    gradeRecordMap.put(studentId + cat.getId(), gradeRecordsByCatAndStudent);
                } else {
                    gradeRecordsByCatAndStudent.add(gradeRecord);
                }
            }
        }

        if (categories == null || categories.size() < 1) {
            return;
        }
        for (Category cat : categories) {
            Integer dropHighest = cat.getDropHighest();
            Integer dropLowest = cat.getDrop_lowest();
            Integer keepHighest = cat.getKeepHighest();
            Long catId = cat.getId();

            if ((dropHighest != null && dropHighest > 0) || (dropLowest != null && dropLowest > 0)
                    || (keepHighest != null && keepHighest > 0)) {

                for (String studentId : studentIds) {
                    // get the student's gradeRecords for this category
                    List<AssignmentGradeRecord> gradesByCategory = new ArrayList<AssignmentGradeRecord>();
                    List<AssignmentGradeRecord> gradeRecordsByCatAndStudent = gradeRecordMap
                            .get(studentId + cat.getId());
                    if (gradeRecordsByCatAndStudent != null) {
                        gradesByCategory.addAll(gradeRecordsByCatAndStudent);

                        int numGrades = gradesByCategory.size();

                        if (dropHighest > 0 && numGrades > dropHighest + dropLowest) {
                            for (int i = 0; i < dropHighest; i++) {
                                AssignmentGradeRecord highest = Collections.max(gradesByCategory,
                                        AssignmentGradeRecord.numericComparator);
                                highest.setDroppedFromGrade(true);
                                gradesByCategory.remove(highest);
                                if (log.isDebugEnabled())
                                    log.debug("dropHighest applied to " + highest);
                            }
                        }

                        if (keepHighest > 0 && numGrades > (gradesByCategory.size() - keepHighest)) {
                            dropLowest = gradesByCategory.size() - keepHighest;
                        }

                        if (dropLowest > 0 && numGrades > dropLowest + dropHighest) {
                            for (int i = 0; i < dropLowest; i++) {
                                AssignmentGradeRecord lowest = Collections.min(gradesByCategory,
                                        AssignmentGradeRecord.numericComparator);
                                lowest.setDroppedFromGrade(true);
                                gradesByCategory.remove(lowest);
                                if (log.isDebugEnabled())
                                    log.debug("dropLowest applied to " + lowest);
                            }
                        }
                    }
                }
                if (log.isDebugEnabled())
                    log.debug("processed " + studentIds.size() + "students in category " + cat.getId());
            }
        }

        if (log.isDebugEnabled())
            log.debug("GradebookManager.applyDropScores took " + (System.currentTimeMillis() - start)
                    + " millis to execute");
    }

    @Override
    public PointsPossibleValidation isPointsPossibleValid(String gradebookUid,
            org.sakaiproject.service.gradebook.shared.Assignment gradebookItem, Double pointsPossible) {
        if (gradebookUid == null) {
            throw new IllegalArgumentException("Null gradebookUid passed to isPointsPossibleValid");
        }
        if (gradebookItem == null) {
            throw new IllegalArgumentException("Null gradebookItem passed to isPointsPossibleValid");
        }

        // At this time, all gradebook items follow the same business rules for
        // points possible (aka relative weight in % gradebooks) so special logic 
        // using the properties of the gradebook item is unnecessary. 
        // In the future, we will have the flexibility to change
        // that behavior without changing the method signature

        // the points possible must be a non-null value greater than 0 with
        // no more than 2 decimal places

        if (pointsPossible == null) {
            return PointsPossibleValidation.INVALID_NULL_VALUE;
        }

        if (pointsPossible.doubleValue() <= 0) {
            return PointsPossibleValidation.INVALID_NUMERIC_VALUE;
        }
        // ensure there are no more than 2 decimal places
        BigDecimal bd = new BigDecimal(pointsPossible.doubleValue());
        bd = bd.setScale(2, BigDecimal.ROUND_HALF_UP); // Two decimal places
        double roundedVal = bd.doubleValue();
        double diff = pointsPossible - roundedVal;
        if (diff != 0) {
            return PointsPossibleValidation.INVALID_DECIMAL;
        }

        return PointsPossibleValidation.VALID;
    }

    /**
    *
    * @param doubleAsString
    * @return a locale-aware Double value representation of the given String
    * @throws ParseException
    */
    private Double convertStringToDouble(String doubleAsString) {
        Double scoreAsDouble = null;
        if (doubleAsString != null && !"".equals(doubleAsString)) {
            try {
                NumberFormat numberFormat = NumberFormat.getInstance(new ResourceLoader().getLocale());
                Number numericScore = numberFormat.parse(doubleAsString.trim());
                scoreAsDouble = numericScore.doubleValue();
            } catch (ParseException e) {
                log.error(e);
            }
        }

        return scoreAsDouble;
    }

    /**
     * Get a list of assignments in the gradebook attached to the given category.
     * Note that each assignment only knows the category by name.
     * 
     * <p>Note also that this is different to {@link BaseHibernateManager#getAssignmentsForCategory(Long)} because this method returns the shared Assignment object.
     * 
     * @param gradebookUid
     * @param categoryName
     * @return
     */
    private List<org.sakaiproject.service.gradebook.shared.Assignment> getAssignments(String gradebookUid,
            String categoryName) {

        List<org.sakaiproject.service.gradebook.shared.Assignment> allAssignments = getAssignments(gradebookUid);
        List<org.sakaiproject.service.gradebook.shared.Assignment> matchingAssignments = new ArrayList<org.sakaiproject.service.gradebook.shared.Assignment>();

        for (org.sakaiproject.service.gradebook.shared.Assignment assignment : allAssignments) {
            if (StringUtils.equals(assignment.getCategoryName(), categoryName)) {
                matchingAssignments.add(assignment);
            }
        }
        return matchingAssignments;
    }

    /**
     * Post an event to Sakai's event table
     * 
     * @param gradebookUid
     * @param assignmentName
     * @param studentUid
     * @param pointsEarned
     * @return
     */
    private void postUpdateGradeEvent(String gradebookUid, String assignmentName, String studentUid,
            Double pointsEarned) {
        if (eventTrackingService != null) {
            eventTrackingService.postEvent("gradebook.updateItemScore", "/gradebook/" + gradebookUid + "/"
                    + assignmentName + "/" + studentUid + "/" + pointsEarned + "/student");
        }
    }

    /**
     * Retrieves the calculated average course grade.
     */
    @Override
    public String getAverageCourseGrade(String gradebookUid) {
        if (gradebookUid == null) {
            throw new IllegalArgumentException("Null gradebookUid passed to getAverageCourseGrade");
        }
        // Check user has permission to invoke method.
        if (!currentUserHasGradeAllPerm(gradebookUid)) {
            StringBuilder sb = new StringBuilder().append("User ").append(authn.getUserUid())
                    .append(" attempted to access the average course grade without permission in gb ")
                    .append(gradebookUid).append(" using gradebookService.getAverageCourseGrade");
            throw new SecurityException(sb.toString());
        }

        String courseGradeLetter = null;
        Gradebook gradebook = getGradebook(gradebookUid);
        if (gradebook != null) {
            CourseGrade courseGrade = getCourseGrade(gradebook.getId());
            Set<String> studentUids = getAllStudentUids(gradebookUid);
            // This call handles the complex rules of which assignments and grades to include in the calculation
            List<CourseGradeRecord> courseGradeRecs = getPointsEarnedCourseGradeRecords(courseGrade, studentUids);
            if (courseGrade != null) {
                // Calculate the course mean grade whether the student grade was manually entered or auto-calculated.
                courseGrade.calculateStatistics(courseGradeRecs, studentUids.size());
                if (courseGrade.getMean() != null) {
                    courseGradeLetter = gradebook.getSelectedGradeMapping().getGrade(courseGrade.getMean());
                }
            }

        }
        return courseGradeLetter;
    }

    /**
     * Updates the order of an assignment
     * 
     * @see GradebookService.updateAssignmentOrder(java.lang.String gradebookUid, java.lang.Long assignmentId, java.lang.Integer order)
     */
    @Override
    public void updateAssignmentOrder(final String gradebookUid, final Long assignmentId, Integer order) {

        if (!getAuthz().isUserAbleToEditAssessments(gradebookUid)) {
            log.error("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                    + " attempted to change the order of assignment " + assignmentId);
            throw new SecurityException("You do not have permission to perform this operation");
        }

        if (order == null) {
            throw new IllegalArgumentException("Order cannot be null");
        }

        final Long gradebookId = getGradebook(gradebookUid).getId();

        //get all assignments for this gradebook
        List<Assignment> assignments = getAssignments(gradebookId, SortType.SORT_BY_SORTING, true);

        //adjust order to be within bounds
        if (order < 0) {
            order = 0;
        } else if (order > assignments.size()) {
            order = assignments.size();
        }

        //find the assignment
        Assignment target = null;
        for (Assignment a : assignments) {
            if (a.getId().equals(assignmentId)) {
                target = a;
                break;
            }
        }

        //add the assignment to the list via a 'pad, remove, add' approach
        assignments.add(null); //ensure size remains the same for the remove
        assignments.remove(target); //remove item
        assignments.add(order, target); //add at ordered position, will shuffle others along

        //the assignments are now in the correct order within the list, we just need to update the sort order for each one
        //create a new list for the assignments we need to update in the database
        List<Assignment> assignmentsToUpdate = new ArrayList<>();

        int i = 0;
        for (Assignment a : assignments) {

            //skip if null
            if (a == null) {
                continue;
            }

            //if the sort order is not the same as the counter, update the order and add to the other list
            //this allows us to skip items that have not had their position changed and saves some db work later on
            //sort order may be null if never previously sorted, so give it the current index
            if (a.getSortOrder() == null || !a.getSortOrder().equals(i)) {
                a.setSortOrder(i);
                assignmentsToUpdate.add(a);
            }

            i++;
        }

        //do the updates
        for (final Assignment assignmentToUpdate : assignmentsToUpdate) {
            getHibernateTemplate().execute(new HibernateCallback() {
                public Object doInHibernate(Session session) throws HibernateException {
                    updateAssignment(assignmentToUpdate, session);
                    return null;
                }
            });
        }

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<GradingEvent> getGradingEvents(final String studentId, final long assignmentId) {

        if (log.isDebugEnabled()) {
            log.debug("getGradingEvents called for studentId:" + studentId);
        }

        List<GradingEvent> rval = new ArrayList<>();

        if (studentId == null) {
            log.debug("No student id was specified.  Returning an empty GradingEvents object");
            return rval;
        }

        HibernateCallback hc = new HibernateCallback() {
            @Override
            public Object doInHibernate(Session session) throws HibernateException, SQLException {
                Query q = session.createQuery(
                        "from GradingEvent as ge where ge.studentId=:studentId and ge.gradableObject.id=:assignmentId");
                q.setParameter("studentId", studentId);
                q.setParameter("assignmentId", assignmentId);
                return q.list();
            }
        };

        rval = (List) getHibernateTemplate().execute(hc);
        return rval;
    }

    @Override
    public Double calculateCategoryScore(CategoryDefinition category, Map<Long, String> gradeMap) {
        List<org.sakaiproject.service.gradebook.shared.Assignment> assignments = category.getAssignmentList();
        return calculateCategoryScore(category.getId(), assignments, gradeMap);
    }

    @Override
    public Double calculateCategoryScore(Long categoryId,
            List<org.sakaiproject.service.gradebook.shared.Assignment> assignments, Map<Long, String> gradeMap) {

        int numScored = 0;
        int numOfAssignments = 0;
        BigDecimal totalEarned = new BigDecimal("0");
        BigDecimal totalPossible = new BigDecimal("0");

        for (org.sakaiproject.service.gradebook.shared.Assignment assignment : assignments) {

            if (categoryId != assignment.getCategoryId()) {
                log.error("Category id: " + categoryId + " did not match assignment categoryId: "
                        + assignment.getCategoryId());
                return null;
            }

            Long assignmentId = assignment.getId();

            String grade = gradeMap.get(assignmentId);

            //only update the variables for the calculation if:
            // 1. the assignment has points to be assigned
            // 2. there is a grade for the student 
            // 3. it's included in course grade calculations
            // 4. it's released to the student (safety check against condition 3)
            if (assignment.getPoints() != null && StringUtils.isNotBlank(grade) && assignment.isCounted()
                    && assignment.isReleased()) {
                totalPossible = totalPossible.add(new BigDecimal(assignment.getPoints().toString()));
                numOfAssignments++;
                numScored++;
            }

            //sanitise grade
            if (StringUtils.isBlank(grade)) {
                grade = "0";
            }

            //update total points earned
            totalEarned = totalEarned.add(new BigDecimal(grade));
        }

        if (numScored == 0 || numOfAssignments == 0 || totalPossible.doubleValue() == 0) {
            return null;
        }

        BigDecimal mean = totalEarned.divide(new BigDecimal(numScored), GradebookService.MATH_CONTEXT)
                .divide((totalPossible.divide(new BigDecimal(numOfAssignments), GradebookService.MATH_CONTEXT)),
                        GradebookService.MATH_CONTEXT)
                .multiply(new BigDecimal("100"));
        return Double.valueOf(mean.doubleValue());
    }

    @Override
    public org.sakaiproject.service.gradebook.shared.CourseGrade getCourseGradeForStudent(String gradebookUid,
            String userUuid) {

        org.sakaiproject.service.gradebook.shared.CourseGrade rval = new org.sakaiproject.service.gradebook.shared.CourseGrade();

        try {
            Gradebook gradebook = getGradebook(gradebookUid);

            //if not released, and not instructor or TA, don't do any work
            //note that this will return a course grade for Instructor and TA even if not released, see SAK-30119
            if (!gradebook.isCourseGradeDisplayed()
                    && (!currentUserHasEditPerm(gradebookUid) || !currentUserHasGradingPerm(gradebookUid))) {
                return rval;
            }

            List<Assignment> assignments = getAssignmentsCounted(gradebook.getId());

            List<CourseGradeRecord> gradeRecords = getPointsEarnedCourseGradeRecords(
                    getCourseGrade(gradebook.getId()), Collections.singletonList(userUuid));

            if (gradeRecords.size() != 1) {
                throw new IllegalStateException("More than one course grade record found for student: " + userUuid);
            }

            CourseGradeRecord gradeRecord = gradeRecords.get(0);

            //set entered grade
            rval.setEnteredGrade(gradeRecord.getEnteredGrade());

            if (!assignments.isEmpty()) {

                //calculated grade
                Double calculatedGrade = gradeRecord.getAutoCalculatedGrade();
                rval.setCalculatedGrade(calculatedGrade.toString());

                //mapped grade
                GradeMapping gradeMap = gradebook.getSelectedGradeMapping();
                String mappedGrade = (String) gradeMap.getGrade(calculatedGrade);
                rval.setMappedGrade(mappedGrade);
            }
        } catch (Exception e) {
            log.error("Error in getCourseGradeForStudent", e);
        }
        return rval;
    }

    @Override
    public List<CourseSection> getViewableSections(String gradebookUid) {
        return this.getAuthz().getViewableSections(gradebookUid);
    }

    @Override
    public void updateGradebookSettings(String gradebookUid, GradebookInformation gbInfo) {
        if (gradebookUid == null) {
            throw new IllegalArgumentException("null gradebookUid " + gradebookUid);
        }

        //must be instructor type person
        if (!currentUserHasEditPerm(gradebookUid)) {
            log.error("AUTHORIZATION FAILURE: User " + getUserUid() + " in gradebook " + gradebookUid
                    + " attempted to edit gb information");
            throw new SecurityException(
                    "You do not have permission to edit gradebook information in site " + gradebookUid);
        }

        final Gradebook gradebook = getGradebook(gradebookUid);
        if (gradebook == null) {
            throw new IllegalArgumentException("There is no gradebook associated with this id: " + gradebookUid);
        }

        //iterate all available grademappings for this gradebook and set the one that we have the ID for
        Set<GradeMapping> gradeMappings = gradebook.getGradeMappings();
        for (GradeMapping gradeMapping : gradeMappings) {
            if (StringUtils.equals(Long.toString(gradeMapping.getId()), gbInfo.getSelectedGradeMappingId())) {
                gradebook.setSelectedGradeMapping(gradeMapping);

                //update the map values
                updateGradeMapping(gradeMapping.getId(), gbInfo.getSelectedGradingScaleBottomPercents());
            }
        }

        //set grade type
        gradebook.setGrade_type(gbInfo.getGradeType());

        //set category type
        gradebook.setCategory_type(gbInfo.getCategoryType());

        //set display release items to students
        gradebook.setAssignmentsDisplayed(gbInfo.isDisplayReleasedGradeItemsToStudents());

        //set course grade display settings
        gradebook.setCourseGradeDisplayed(gbInfo.isCourseGradeDisplayed());
        gradebook.setCourseLetterGradeDisplayed(gbInfo.isCourseLetterGradeDisplayed());
        gradebook.setCoursePointsDisplayed(gbInfo.isCoursePointsDisplayed());
        gradebook.setCourseAverageDisplayed(gbInfo.isCourseAverageDisplayed());

        List<CategoryDefinition> newCategoryDefinitions = gbInfo.getCategories();

        //if we have categories and they are weighted, check the weightings sum up to 100% (or 1 since it's a fraction)
        if (gradebook.getCategory_type() == GradebookService.CATEGORY_TYPE_WEIGHTED_CATEGORY) {
            double totalWeight = 0;
            for (CategoryDefinition newDef : newCategoryDefinitions) {

                if (newDef.getWeight() == null) {
                    throw new IllegalArgumentException(
                            "No weight specified for a category, but weightings enabled");
                }

                totalWeight += newDef.getWeight();
            }
            if (Math.rint(totalWeight) != 1) {
                throw new IllegalArgumentException("Weightings for the categories do not equal 100%");
            }
        }

        //get current categories and build a mapping list of Category.id to Category
        List<Category> currentCategories = this.getCategories(gradebook.getId());
        Map<Long, Category> currentCategoryMap = new HashMap<>();
        for (Category c : currentCategories) {
            currentCategoryMap.put(c.getId(), c);
        }

        //compare current list with given list, add/update/remove as required
        //Rules:
        //If category does not have an ID it is new
        //If category has an ID it is to be updated. Update and remove from currentCategoryMap.
        //Any categories remaining in currentCategoryMap are to be removed.
        for (CategoryDefinition newDef : newCategoryDefinitions) {

            //preprocessing and validation
            //Rule 1: If category has no name, it is to be removed/skipped
            //Note that we no longer set weights to 0 even if unweighted category type selected. The weights are not considered if its not a weighted category type
            //so this allows us to switch back and forth between types without losing information

            if (StringUtils.isBlank(newDef.getName())) {
                continue;
            }

            //new
            if (newDef.getId() == null) {
                this.createCategory(gradebook.getId(), newDef.getName(), newDef.getWeight(),
                        newDef.getDrop_lowest(), newDef.getDropHighest(), newDef.getKeepHighest(),
                        newDef.isExtraCredit());
                continue;
            }

            //update
            else {
                Category existing = currentCategoryMap.get(newDef.getId());
                existing.setName(newDef.getName());
                existing.setWeight(newDef.getWeight());
                existing.setDrop_lowest(newDef.getDrop_lowest());
                existing.setDropHighest(newDef.getDropHighest());
                existing.setKeepHighest(newDef.getKeepHighest());
                existing.setExtraCredit(newDef.isExtraCredit());
                this.updateCategory(existing);

                //remove from currentCategoryMap so we know not to delete it
                currentCategoryMap.remove(newDef.getId());
                continue;
            }

        }

        //handle deletes
        //anything left in currentCategoryMap was not included in the new list, delete them
        for (Entry<Long, Category> cat : currentCategoryMap.entrySet()) {
            this.removeCategory(cat.getKey());
        }

        //no need to set assignments, gbInfo doesn't update them

        //persist
        this.updateGradebook(gradebook);

    }

    public void setEventTrackingService(EventTrackingService eventTrackingService) {
        this.eventTrackingService = eventTrackingService;
    }

    public Authz getAuthz() {
        return authz;
    }

    public void setAuthz(Authz authz) {
        this.authz = authz;
    }

    public GradebookPermissionService getGradebookPermissionService() {
        return gradebookPermissionService;
    }

    public void setGradebookPermissionService(GradebookPermissionService gradebookPermissionService) {
        this.gradebookPermissionService = gradebookPermissionService;
    }

    @Override
    public Set getGradebookGradeMappings(final Long gradebookId) {
        return (Set) getHibernateTemplate().execute(new HibernateCallback() {
            @Override
            public Set doInHibernate(Session session) throws HibernateException {
                Gradebook gradebook = (Gradebook) session.load(Gradebook.class, gradebookId);
                Hibernate.initialize(gradebook.getGradeMappings());
                return gradebook.getGradeMappings();
            }
        });
    }

    @Override
    public Set getGradebookGradeMappings(final String gradebookUid) {
        final Long gradebookId = getGradebook(gradebookUid).getId();
        return this.getGradebookGradeMappings(gradebookId);
    }

    /**
     * Map a set of GradeMapping to a list of GradeMappingDefinition
     * @param gradeMappings set of GradeMapping
     * @return list of GradeMappingDefinition
     */
    private List<GradeMappingDefinition> getGradebookGradeMappings(Set<GradeMapping> gradeMappings) {
        List<GradeMappingDefinition> rval = new ArrayList<>();

        for (GradeMapping mapping : gradeMappings) {
            rval.add(new GradeMappingDefinition(mapping.getId(), mapping.getName(), mapping.getGradeMap(),
                    mapping.getDefaultBottomPercents()));
        }
        return rval;

    }

}