package org.sakaiproject.assignment.impl; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; 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.api.app.scheduler.DelayedInvocation; import org.sakaiproject.api.app.scheduler.ScheduledInvocationManager; import org.sakaiproject.assignment.api.Assignment; import org.sakaiproject.assignment.api.AssignmentConstants; import org.sakaiproject.assignment.api.AssignmentPeerAssessmentService; import org.sakaiproject.assignment.api.AssignmentService; import org.sakaiproject.assignment.api.AssignmentSubmission; import org.sakaiproject.assignment.api.AssignmentSubmissionEdit; import org.sakaiproject.assignment.api.model.PeerAssessmentItem; import org.sakaiproject.authz.api.SecurityAdvisor; import org.sakaiproject.authz.api.SecurityService; import org.sakaiproject.authz.api.SecurityAdvisor.SecurityAdvice; import org.sakaiproject.exception.IdUnusedException; import org.sakaiproject.exception.InUseException; import org.sakaiproject.exception.PermissionException; import org.sakaiproject.time.api.Time; import org.sakaiproject.time.api.TimeService; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.user.api.User; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; public class AssignmentPeerAssessmentServiceImpl extends HibernateDaoSupport implements AssignmentPeerAssessmentService { private static Log log = LogFactory.getLog(AssignmentPeerAssessmentServiceImpl.class); private ScheduledInvocationManager scheduledInvocationManager; private TimeService timeService; protected AssignmentService assignmentService; private SecurityService securityService = null; private SessionManager sessionManager; public void init(){ } public void destroy(){ } public void schedulePeerReview(String assignmentId){ //first remove any previously scheduled reviews: removeScheduledPeerReview(assignmentId); //now schedule a time for the review to be setup Assignment assignment; try { assignment = assignmentService.getAssignment(assignmentId); if(!assignment.getDraft() && assignment.getAllowPeerAssessment()){ Time assignmentCloseTime = assignment.getCloseTime(); Time openTime = null; if(assignmentCloseTime != null){ openTime = timeService.newTime(assignmentCloseTime.getTime()); } // Schedule the new notification if (openTime != null){ scheduledInvocationManager.createDelayedInvocation(openTime, "org.sakaiproject.assignment.api.AssignmentPeerAssessmentService", assignmentId); } } } catch (IdUnusedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (PermissionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void removeScheduledPeerReview(String assignmentId){ // Remove any existing notifications for this area DelayedInvocation[] fdi = scheduledInvocationManager.findDelayedInvocations("org.sakaiproject.assignment.api.AssignmentPeerAssessmentService", assignmentId); if (fdi != null && fdi.length > 0) { for (DelayedInvocation d : fdi) { scheduledInvocationManager.deleteDelayedInvocation(d.uuid); } } } /** * Method called by the scheduledInvocationManager */ public void execute(String opaqueContext){ try { //for group assignments, we need to have a user ID, otherwise, an exception is thrown: sessionManager.getCurrentSession().setUserEid("admin"); sessionManager.getCurrentSession().setUserId("admin"); Assignment assignment = assignmentService.getAssignment(opaqueContext); if(assignment.getAllowPeerAssessment() && !assignment.getDraft()){ int numOfReviews = assignment.getPeerAssessmentNumReviews(); List<AssignmentSubmission> submissions = (List<AssignmentSubmission>) assignmentService.getSubmissions(assignment); //keep a map of submission ids to look up possible existing peer assessments Map<String, AssignmentSubmission> submissionIdMap = new HashMap<String, AssignmentSubmission>(); //keep track of who has been assigned an assessment Map<String, Map<String, PeerAssessmentItem>> assignedAssessmentsMap = new HashMap<String, Map<String, PeerAssessmentItem>>(); //keep track of how many assessor's each student has Map<String, Integer> studentAssessorsMap = new HashMap<String, Integer>(); List<User> submitterUsersList = (List<User>) assignmentService.allowAddSubmissionUsers(assignment.getReference()); List<String> submitterIdsList = new ArrayList<String>(); if(submitterUsersList != null){ for(User u : submitterUsersList){ submitterIdsList.add(u.getId()); } } //loop through the assignment submissions and setup the maps and lists for(AssignmentSubmission s : submissions){ if(s.getTimeSubmitted() != null //check if the submission is submitted, if not, see if there is any submission data to review (i.e. draft was auto submitted) && (s.getSubmitted() || ((s.getSubmittedText() != null && !"".equals(s.getSubmittedText().trim()) || (s.getSubmittedAttachments() != null && s.getSubmittedAttachments().size() > 0)))) && submitterIdsList.contains(s.getSubmitterId()) && !"admin".equals(s.getSubmitterId())){ //only deal with users in the submitter's list submissionIdMap.put(s.getId(), s); assignedAssessmentsMap.put(s.getSubmitterId(), new HashMap<String, PeerAssessmentItem>()); studentAssessorsMap.put(s.getSubmitterId(), 0); } } //this could be an update to an existing assessment... just make sure to grab any existing //review items first List<PeerAssessmentItem> existingItems = getPeerAssessmentItems(submissionIdMap.keySet()); List<PeerAssessmentItem> removeItems = new ArrayList<PeerAssessmentItem>(); //remove all empty items to start from scratch: for (Iterator iterator = existingItems.iterator(); iterator .hasNext();) { PeerAssessmentItem peerAssessmentItem = (PeerAssessmentItem) iterator.next(); if(peerAssessmentItem.getScore() == null && (peerAssessmentItem.getComment() == null || "".equals(peerAssessmentItem.getComment().trim()))){ removeItems.add(peerAssessmentItem); iterator.remove(); } } if(removeItems.size() > 0){ getHibernateTemplate().deleteAll(removeItems); } //loop through the items and update the map values: for(PeerAssessmentItem p : existingItems){ if(submissionIdMap.containsKey(p.getSubmissionId())){ //first, add this assessment to the AssignedAssessmentsMap AssignmentSubmission s = submissionIdMap.get(p.getSubmissionId()); //Next, increment the count for studentAssessorsMap Integer count = studentAssessorsMap.get(s.getSubmitterId()); if(count == null){ //probably not possible, just check count = 0; } //check if the count is less than num of reviews before added another one, //otherwise, we need to delete this one (if it's empty) if(count < numOfReviews || p.getScore() != null || p.getComment() != null){ count++; studentAssessorsMap.put(s.getSubmitterId(), count); Map<String, PeerAssessmentItem> peerAssessments = assignedAssessmentsMap.get(p.getAssessorUserId()); if(peerAssessments == null){ //probably not possible, but just check peerAssessments = new HashMap<String, PeerAssessmentItem>(); } peerAssessments.put(p.getSubmissionId(), p); assignedAssessmentsMap.put(p.getAssessorUserId(), peerAssessments); }else{ //this shoudln't happen since the code above removes all empty assessments, but just in case: getHibernateTemplate().delete(p); } }else{ //this isn't realy possible since we looked up the peer assessments by submission id log.error("AssignmentPeerAssessmentServiceImpl: found a peer assessment with an invalid session id: " + p.getSubmissionId()); } } //ok now that we have any existing assigned reviews accounted for, let's make sure that the number of reviews is setup properly, //if not, add some //let's get a random order of submission IDs so we can have a random assigning algorithm List<String> randomSubmissionIds = new ArrayList<String>(submissionIdMap.keySet()); Collections.shuffle(randomSubmissionIds); List<PeerAssessmentItem> newItems = new ArrayList<PeerAssessmentItem>(); int i = 0; for(String submissionId : randomSubmissionIds){ AssignmentSubmission s = submissionIdMap.get(submissionId); //first find out how many existing items exist for this user: Integer assignedCount = studentAssessorsMap.get(s.getSubmitterId()); //by creating a tailing list (snake style), we eliminate the issue where you can be stuck with //a submission and the same submission user left, making for uneven distributions of submission reviews List<String> snakeSubmissionList = new ArrayList<String>(randomSubmissionIds.subList(i, randomSubmissionIds.size())); if(i > 0){ snakeSubmissionList.addAll(new ArrayList<String>(randomSubmissionIds.subList(0, i))); } while(assignedCount < numOfReviews){ //we need to add more reviewers for this user's submission String lowestAssignedAssessor = findLowestAssignedAssessor(assignedAssessmentsMap,s.getSubmitterId(), submissionId, snakeSubmissionList, submissionIdMap); if(lowestAssignedAssessor != null){ Map<String, PeerAssessmentItem> assessorsAssessmentMap = assignedAssessmentsMap.get(lowestAssignedAssessor); if(assessorsAssessmentMap == null){ assessorsAssessmentMap = new HashMap<String, PeerAssessmentItem>(); } PeerAssessmentItem newItem = new PeerAssessmentItem(); newItem.setAssessorUserId(lowestAssignedAssessor); newItem.setSubmissionId(submissionId); newItem.setAssignmentId(assignment.getId()); newItems.add(newItem); assessorsAssessmentMap.put(submissionId, newItem); assignedAssessmentsMap.put(lowestAssignedAssessor, assessorsAssessmentMap); //update this submission user's count: assignedCount++; studentAssessorsMap.put(submissionId, assignedCount); }else{ break; } } i++; } if(newItems.size() > 0){ getHibernateTemplate().saveOrUpdateAll(newItems); } } } catch (IdUnusedException e) { log.error(e.getMessage(), e); } catch (PermissionException e) { log.error(e.getMessage(), e); }finally{ sessionManager.getCurrentSession().setUserEid(null); sessionManager.getCurrentSession().setUserId(null); } } private String findLowestAssignedAssessor(Map<String, Map<String, PeerAssessmentItem>> peerAssessments, String assesseeId, String assesseeSubmissionId, List<String> snakeSubmissionList, Map<String, AssignmentSubmission> submissionIdMap){//find the lowest count of assigned submissions String lowestAssignedAssessor = null; Integer lowestAssignedAssessorCount = null; for(String sId : snakeSubmissionList){ AssignmentSubmission s = submissionIdMap.get(sId); //do not include assesseeId (aka the user being assessed) if(!assesseeId.equals(s.getSubmitterId()) && (lowestAssignedAssessorCount == null || peerAssessments.get(s.getSubmitterId()).keySet().size() < lowestAssignedAssessorCount)){ //check if this user already has a peer assessment for this assessee boolean found = false; for(PeerAssessmentItem p : peerAssessments.get(s.getSubmitterId()).values()){ if(p.getSubmissionId().equals(assesseeSubmissionId)){ found = true; break; } } if(!found){ lowestAssignedAssessorCount = peerAssessments.get(s.getSubmitterId()).keySet().size(); lowestAssignedAssessor = s.getSubmitterId(); } } } return lowestAssignedAssessor; } public List<PeerAssessmentItem> getPeerAssessmentItems(final Collection<String> submissionsIds){ if(submissionsIds == null || submissionsIds.size() == 0){ //return an empty list return new ArrayList<PeerAssessmentItem>(); } HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { Query q = session.getNamedQuery("findPeerAssessmentItemsBySubmissions"); q.setParameterList("submissionIds", submissionsIds); return q.list(); } }; return (List<PeerAssessmentItem>) getHibernateTemplate().execute(hcb); } public List<PeerAssessmentItem> getPeerAssessmentItems(final String assignmentId, final String assessorUserId){ if(assignmentId == null || assessorUserId == null){ //return an empty list return new ArrayList<PeerAssessmentItem>(); } HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { Query q = session.getNamedQuery("findPeerAssessmentItemsByUserAndAssignment"); q.setParameter("assignmentId", assignmentId); q.setParameter("assessorUserId", assessorUserId); return q.list(); } }; return (List<PeerAssessmentItem>) getHibernateTemplate().execute(hcb); } public List<PeerAssessmentItem> getPeerAssessmentItems(final String submissionId){ if(submissionId == null || "".equals(submissionId)){ //return an empty list return new ArrayList<PeerAssessmentItem>(); } HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { Query q = session.getNamedQuery("findPeerAssessmentItemsBySubmissionId"); q.setParameter("submissionId", submissionId); return q.list(); } }; return (List<PeerAssessmentItem>) getHibernateTemplate().execute(hcb); } public List<PeerAssessmentItem> getPeerAssessmentItemsByAssignmentId(final String assignmentId){ if(assignmentId == null || "".equals(assignmentId)){ //return an empty list return new ArrayList<PeerAssessmentItem>(); } HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { Query q = session.getNamedQuery("findPeerAssessmentItemsByAssignmentId"); q.setParameter("assignmentId", assignmentId); return q.list(); } }; return (List<PeerAssessmentItem>) getHibernateTemplate().execute(hcb); } public PeerAssessmentItem getPeerAssessmentItem(final String submissionId, final String assessorUserId){ if(submissionId == null || assessorUserId == null){ //return an empty list return null; } HibernateCallback hcb = new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException, SQLException { Query q = session.getNamedQuery("findPeerAssessmentItemsByUserAndSubmission"); q.setParameter("submissionId", submissionId); q.setParameter("assessorUserId", assessorUserId); return q.list(); } }; List<PeerAssessmentItem> results = (List<PeerAssessmentItem>) getHibernateTemplate().execute(hcb); if(results != null && results.size() == 1){ return results.get(0); }else{ return null; } } public void savePeerAssessmentItem(PeerAssessmentItem item){ if(item != null && item.getAssessorUserId() != null && item.getSubmissionId() != null){ getHibernateTemplate().saveOrUpdate(item); } } public void updateScore(String submissionId){ List<PeerAssessmentItem> items = getPeerAssessmentItems(submissionId); if(items != null){ //scores are stored w/o decimal points, so a score of 3.4 is stored as 34 in the DB //add all the scores together and divide it by the number of scores added. Then round. Integer totalScore = 0; int denominator = 0; for(PeerAssessmentItem item : items){ if(!item.isRemoved() && item.getScore() != null){ totalScore += item.getScore(); denominator++; } } if(denominator > 0){ totalScore = Math.round(totalScore/denominator); }else{ totalScore = null; } SecurityAdvisor sa = new SecurityAdvisor() { public SecurityAdvice isAllowed(String userId, String function, String reference) { if(AssignmentService.SECURE_GRADE_ASSIGNMENT_SUBMISSION.equals(function) || AssignmentService.SECURE_UPDATE_ASSIGNMENT.equals(function)){ return SecurityAdvice.ALLOWED; }else{ return SecurityAdvice.PASS; } } }; try { securityService.pushAdvisor(sa); AssignmentSubmissionEdit edit = assignmentService.editSubmission(submissionId); String totleScoreStr = null; if(totalScore != null){ totleScoreStr = totalScore.toString(); } edit.setGrade(totleScoreStr); edit.setGraded(true); assignmentService.commitEdit(edit); } catch (IdUnusedException e) { log.error(e.getMessage(), e); } catch (InUseException e) { log.error(e.getMessage(), e); } catch (PermissionException e) { log.error(e.getMessage(), e); }finally { // remove advisor securityService.popAdvisor(sa); } } } public void setScheduledInvocationManager( ScheduledInvocationManager scheduledInvocationManager) { this.scheduledInvocationManager = scheduledInvocationManager; } public void setTimeService(TimeService timeService) { this.timeService = timeService; } public void setAssignmentService(AssignmentService assignmentService){ this.assignmentService = assignmentService; } public SecurityService getSecurityService() { return securityService; } public void setSecurityService(SecurityService securityService) { this.securityService = securityService; } public SessionManager getSessionManager() { return sessionManager; } public void setSessionManager(SessionManager sessionManager) { this.sessionManager = sessionManager; } }