/********************************************************************************** * * $Id: GradebookFrameworkServiceImpl.java 105077 2012-02-24 22:54:29Z ottenhoff@longsight.com $ * *********************************************************************************** * * Copyright (c) 2007, 2008, 2009 The Sakai Foundation * * 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.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.HibernateException; import org.hibernate.Query; import org.hibernate.Session; import org.sakaiproject.service.gradebook.shared.GradebookExistsException; import org.sakaiproject.service.gradebook.shared.GradebookFrameworkService; import org.sakaiproject.service.gradebook.shared.GradebookNotFoundException; import org.sakaiproject.service.gradebook.shared.GradebookService; import org.sakaiproject.service.gradebook.shared.GradingScaleDefinition; import org.sakaiproject.tool.gradebook.CourseGrade; import org.sakaiproject.tool.gradebook.GradeMapping; import org.sakaiproject.tool.gradebook.Gradebook; import org.sakaiproject.tool.gradebook.GradingScale; import org.sakaiproject.tool.gradebook.LetterGradeMapping; import org.sakaiproject.tool.gradebook.LetterGradePercentMapping; import org.sakaiproject.tool.gradebook.LetterGradePlusMinusMapping; import org.sakaiproject.tool.gradebook.PassNotPassMapping; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.HibernateTemplate; public class GradebookFrameworkServiceImpl extends BaseHibernateManager implements GradebookFrameworkService { private static final Log log = LogFactory.getLog(GradebookFrameworkServiceImpl.class); public static final String UID_OF_DEFAULT_GRADING_SCALE_PROPERTY = "uidOfDefaultGradingScale"; public void addGradebook(final String uid, final String name) { if(isGradebookDefined(uid)) { log.warn("You can not add a gradebook with uid=" + uid + ". That gradebook already exists."); throw new GradebookExistsException("You can not add a gradebook with uid=" + uid + ". That gradebook already exists."); } if (log.isDebugEnabled()) log.debug("Adding gradebook uid=" + uid + " by userUid=" + getUserUid()); createDefaultLetterGradeMapping(getHardDefaultLetterMapping()); getHibernateTemplate().execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { // Get available grade mapping templates. List gradingScales = session.createQuery("from GradingScale as gradingScale where gradingScale.unavailable=false").list(); // The application won't be able to run without grade mapping // templates, so if for some reason none have been defined yet, // do that now. if (gradingScales.isEmpty()) { if (log.isInfoEnabled()) log.info("No Grading Scale defined yet. This is probably because you have upgraded or you are working with a new database. Default grading scales will be created. Any customized system-wide grade mappings you may have defined in previous versions will have to be reconfigured."); gradingScales = GradebookFrameworkServiceImpl.this.addDefaultGradingScales(session); } // Create and save the gradebook Gradebook gradebook = new Gradebook(name); gradebook.setUid(uid); session.save(gradebook); // Create the course grade for the gradebook CourseGrade cg = new CourseGrade(); cg.setGradebook(gradebook); session.save(cg); // According to the specification, Display Assignment Grades is // on by default, and Display course grade is off. gradebook.setAssignmentsDisplayed(true); gradebook.setCourseGradeDisplayed(false); String defaultScaleUid = GradebookFrameworkServiceImpl.this.getPropertyValue(UID_OF_DEFAULT_GRADING_SCALE_PROPERTY); // Add and save grade mappings based on the templates. GradeMapping defaultGradeMapping = null; Set gradeMappings = new HashSet(); for (Iterator iter = gradingScales.iterator(); iter.hasNext();) { GradingScale gradingScale = (GradingScale)iter.next(); GradeMapping gradeMapping = new GradeMapping(gradingScale); gradeMapping.setGradebook(gradebook); session.save(gradeMapping); gradeMappings.add(gradeMapping); if (gradingScale.getUid().equals(defaultScaleUid)) { defaultGradeMapping = gradeMapping; } } // Check for null default. if (defaultGradeMapping == null) { defaultGradeMapping = (GradeMapping)gradeMappings.iterator().next(); if (log.isWarnEnabled()) log.warn("No default GradeMapping found for new Gradebook=" + gradebook.getUid() + "; will set default to " + defaultGradeMapping.getName()); } gradebook.setSelectedGradeMapping(defaultGradeMapping); // The Hibernate mapping as of Sakai 2.2 makes this next // call meaningless when it comes to persisting changes at // the end of the transaction. It is, however, needed for // the mappings to be seen while the transaction remains // uncommitted. gradebook.setGradeMappings(gradeMappings); gradebook.setGrade_type(GradebookService.GRADE_TYPE_POINTS); gradebook.setCategory_type(GradebookService.CATEGORY_TYPE_NO_CATEGORY); // Update the gradebook with the new selected grade mapping session.update(gradebook); return null; } }); } private List addDefaultGradingScales(Session session) throws HibernateException { List gradingScales = new ArrayList(); // Base the default set of templates on the old // statically defined GradeMapping classes. GradeMapping[] oldGradeMappings = { new LetterGradeMapping(), new LetterGradePlusMinusMapping(), new PassNotPassMapping() }; for (int i = 0; i < oldGradeMappings.length; i++) { GradeMapping sampleMapping = oldGradeMappings[i]; sampleMapping.setDefaultValues(); GradingScale gradingScale = new GradingScale(); String uid = sampleMapping.getClass().getName(); uid = uid.substring(uid.lastIndexOf('.') + 1); gradingScale.setUid(uid); gradingScale.setUnavailable(false); gradingScale.setName(sampleMapping.getName()); gradingScale.setGrades(new ArrayList(sampleMapping.getGrades())); gradingScale.setDefaultBottomPercents(new HashMap(sampleMapping.getGradeMap())); session.save(gradingScale); if (log.isInfoEnabled()) log.info("Added Grade Mapping " + gradingScale.getUid()); gradingScales.add(gradingScale); } setDefaultGradingScale("LetterGradePlusMinusMapping"); session.flush(); return gradingScales; } public void setAvailableGradingScales(final Collection gradingScaleDefinitions) { getHibernateTemplate().execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { mergeGradeMappings(gradingScaleDefinitions, session); return null; } }); } public void setDefaultGradingScale(String uid) { setPropertyValue(UID_OF_DEFAULT_GRADING_SCALE_PROPERTY, uid); } private void copyDefinitionToScale(GradingScaleDefinition bean, GradingScale gradingScale) { gradingScale.setUnavailable(false); gradingScale.setName(bean.getName()); gradingScale.setGrades(bean.getGrades()); Map defaultBottomPercents = new HashMap(); Iterator gradesIter = bean.getGrades().iterator(); Iterator defaultBottomPercentsIter = bean.getDefaultBottomPercents().iterator(); while (gradesIter.hasNext() && defaultBottomPercentsIter.hasNext()) { String grade = (String)gradesIter.next(); Double value = (Double)defaultBottomPercentsIter.next(); defaultBottomPercents.put(grade, value); } gradingScale.setDefaultBottomPercents(defaultBottomPercents); } private void mergeGradeMappings(Collection gradingScaleDefinitions, Session session) throws HibernateException { Map newMappingDefinitionsMap = new HashMap(); HashSet uidsToSet = new HashSet(); for (Iterator iter = gradingScaleDefinitions.iterator(); iter.hasNext(); ) { GradingScaleDefinition bean = (GradingScaleDefinition)iter.next(); newMappingDefinitionsMap.put(bean.getUid(), bean); uidsToSet.add(bean.getUid()); } // Until we move to Hibernate 3 syntax, we need to update one record at a time. Query q; List gmtList; // Toggle any scales that are no longer specified. q = session.createQuery("from GradingScale as gradingScale where gradingScale.uid not in (:uidList) and gradingScale.unavailable=false"); q.setParameterList("uidList", uidsToSet); gmtList = q.list(); for (Iterator iter = gmtList.iterator(); iter.hasNext(); ) { GradingScale gradingScale = (GradingScale)iter.next(); gradingScale.setUnavailable(true); session.update(gradingScale); if (log.isInfoEnabled()) log.info("Set Grading Scale " + gradingScale.getUid() + " unavailable"); } // Modify any specified scales that already exist. q = session.createQuery("from GradingScale as gradingScale where gradingScale.uid in (:uidList)"); q.setParameterList("uidList", uidsToSet); gmtList = q.list(); for (Iterator iter = gmtList.iterator(); iter.hasNext(); ) { GradingScale gradingScale = (GradingScale)iter.next(); copyDefinitionToScale((GradingScaleDefinition)newMappingDefinitionsMap.get(gradingScale.getUid()), gradingScale); uidsToSet.remove(gradingScale.getUid()); session.update(gradingScale); if (log.isInfoEnabled()) log.info("Updated Grading Scale " + gradingScale.getUid()); } // Add any new scales. for (Iterator iter = uidsToSet.iterator(); iter.hasNext(); ) { String uid = (String)iter.next(); GradingScale gradingScale = new GradingScale(); gradingScale.setUid(uid); GradingScaleDefinition bean = (GradingScaleDefinition)newMappingDefinitionsMap.get(uid); copyDefinitionToScale(bean, gradingScale); session.save(gradingScale); if (log.isInfoEnabled()) log.info("Added Grading Scale " + gradingScale.getUid()); } session.flush(); } public void deleteGradebook(final String uid) throws GradebookNotFoundException { if (log.isDebugEnabled()) log.debug("Deleting gradebook uid=" + uid + " by userUid=" + getUserUid()); final Long gradebookId = getGradebook(uid).getId(); // Worse of both worlds code ahead. We've been quick-marched // into Hibernate 3 sessions, but we're also having to use classic query // parsing -- which keeps us from being able to use either Hibernate's new-style // bulk delete queries or Hibernate's old-style session.delete method. // Instead, we're stuck with going through the Spring template for each // deletion one at a time. HibernateTemplate hibTempl = getHibernateTemplate(); // int numberDeleted = hibTempl.bulkUpdate("delete GradingEvent as ge where ge.gradableObject.gradebook.id=?", gradebookId); // log.warn("GradingEvent numberDeleted=" + numberDeleted); List toBeDeleted; int numberDeleted; toBeDeleted = hibTempl.find("from GradingEvent as ge where ge.gradableObject.gradebook.id=?", gradebookId); numberDeleted = toBeDeleted.size(); hibTempl.deleteAll(toBeDeleted); if (log.isDebugEnabled()) log.debug("Deleted " + numberDeleted + " grading events"); toBeDeleted = hibTempl.find("from AbstractGradeRecord as gr where gr.gradableObject.gradebook.id=?", gradebookId); numberDeleted = toBeDeleted.size(); hibTempl.deleteAll(toBeDeleted); if (log.isDebugEnabled()) log.debug("Deleted " + numberDeleted + " grade records"); toBeDeleted = hibTempl.find("from GradableObject as go where go.gradebook.id=?", gradebookId); numberDeleted = toBeDeleted.size(); hibTempl.deleteAll(toBeDeleted); if (log.isDebugEnabled()) log.debug("Deleted " + numberDeleted + " gradable objects"); Gradebook gradebook = (Gradebook)hibTempl.load(Gradebook.class, gradebookId); gradebook.setSelectedGradeMapping(null); toBeDeleted = hibTempl.find("from GradeMapping as gm where gm.gradebook.id=?", gradebookId); numberDeleted = toBeDeleted.size(); hibTempl.deleteAll(toBeDeleted); if (log.isDebugEnabled()) log.debug("Deleted " + numberDeleted + " grade mappings"); hibTempl.delete(gradebook); hibTempl.flush(); hibTempl.clear(); } private void createDefaultLetterGradeMapping(final Map gradeMap) { if(getDefaultLetterGradePercentMapping() == null) { Set keySet = gradeMap.keySet(); if(keySet.size() != GradebookService.validLetterGrade.length) //we only consider letter grade with -/+ now. throw new IllegalArgumentException("gradeMap doesn't have right size in BaseHibernateManager.createDefaultLetterGradePercentMapping"); if(validateLetterGradeMapping(gradeMap) == false) throw new IllegalArgumentException("gradeMap contains invalid letter in BaseHibernateManager.createDefaultLetterGradePercentMapping"); HibernateCallback hc = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { LetterGradePercentMapping lgpm = new LetterGradePercentMapping(); session.save(lgpm); Map saveMap = new HashMap(); for(Iterator iter = gradeMap.keySet().iterator(); iter.hasNext();) { String key = (String) iter.next(); saveMap.put(key, gradeMap.get(key)); } if (lgpm != null) { lgpm.setGradeMap(saveMap); lgpm.setMappingType(1); session.update(lgpm); } return null; } }; getHibernateTemplate().execute(hc); } } private Map getHardDefaultLetterMapping() { Map gradeMap = new HashMap(); gradeMap.put("A+", Double.valueOf(100)); gradeMap.put("A", Double.valueOf(95)); gradeMap.put("A-", Double.valueOf(90)); gradeMap.put("B+", Double.valueOf(87)); gradeMap.put("B", Double.valueOf(83)); gradeMap.put("B-", Double.valueOf(80)); gradeMap.put("C+", Double.valueOf(77)); gradeMap.put("C", Double.valueOf(73)); gradeMap.put("C-", Double.valueOf(70)); gradeMap.put("D+", Double.valueOf(67)); gradeMap.put("D", Double.valueOf(63)); gradeMap.put("D-", Double.valueOf(60)); gradeMap.put("F", Double.valueOf(0.0)); return gradeMap; } }