/** * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at the * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Initial code contributed and copyrighted by<br> * frentix GmbH, http://www.frentix.com * <p> */ package org.olat.course.assessment.manager; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; import org.olat.core.CoreSpringFactory; import org.olat.core.commons.persistence.DBFactory; import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.logging.activity.StringResourceableType; import org.olat.core.logging.activity.ThreadLocalUserActivityLogger; import org.olat.core.util.StringHelper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.core.util.event.GenericEventListener; import org.olat.core.util.resource.OresHelper; import org.olat.course.CourseFactory; import org.olat.course.ICourse; import org.olat.course.assessment.AssessmentChangedEvent; import org.olat.course.assessment.AssessmentHelper; import org.olat.course.assessment.AssessmentLoggingAction; import org.olat.course.assessment.AssessmentManager; import org.olat.course.assessment.model.AssessmentNodeData; import org.olat.course.auditing.UserNodeAuditManager; import org.olat.course.certificate.CertificateTemplate; import org.olat.course.certificate.CertificatesManager; import org.olat.course.certificate.model.CertificateInfos; import org.olat.course.groupsandrights.CourseGroupManager; import org.olat.course.nodes.AssessableCourseNode; import org.olat.course.nodes.CourseNode; import org.olat.course.run.environment.CourseEnvironment; import org.olat.course.run.scoring.ScoreAccounting; import org.olat.course.run.scoring.ScoreEvaluation; import org.olat.course.run.userview.UserCourseEnvironment; import org.olat.group.BusinessGroup; import org.olat.modules.assessment.AssessmentEntry; import org.olat.modules.assessment.AssessmentService; import org.olat.modules.assessment.model.AssessmentEntryStatus; import org.olat.repository.RepositoryEntry; import org.olat.util.logging.activity.LoggingResourceable; /** * * Initial date: 20.07.2015<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ public class CourseAssessmentManagerImpl implements AssessmentManager { private static final Float FLOAT_ZERO = new Float(0); private static final Integer INTEGER_ZERO = new Integer(0); private final CourseGroupManager cgm; private final AssessmentService assessmentService; private final CertificatesManager certificatesManager; private final EfficiencyStatementManager efficiencyStatementManager; public CourseAssessmentManagerImpl(CourseGroupManager cgm) { this.cgm = cgm; assessmentService = CoreSpringFactory.getImpl(AssessmentService.class); certificatesManager = CoreSpringFactory.getImpl(CertificatesManager.class); efficiencyStatementManager = CoreSpringFactory.getImpl(EfficiencyStatementManager.class); } private AssessmentEntry getOrCreate(Identity assessedIdentity, CourseNode courseNode) { return assessmentService.getOrCreateAssessmentEntry(assessedIdentity, null, cgm.getCourseEntry(), courseNode.getIdent(), courseNode.getReferencedRepositoryEntry()); } private AssessmentEntry getOrCreate(Identity assessedIdentity, String subIdent, RepositoryEntry referenceEntry) { return assessmentService.getOrCreateAssessmentEntry(assessedIdentity, null, cgm.getCourseEntry(), subIdent, referenceEntry); } @Override public List<AssessmentEntry> getAssessmentEntries(CourseNode courseNode) { return assessmentService.loadAssessmentEntriesBySubIdent(cgm.getCourseEntry(), courseNode.getIdent()); } @Override public List<AssessmentEntry> getAssessmentEntriesWithStatus(CourseNode courseNode, AssessmentEntryStatus status, boolean excludeZeroScore) { return assessmentService.loadAssessmentEntriesBySubIdentWithStatus(cgm.getCourseEntry(), courseNode.getIdent(), status, excludeZeroScore); } @Override public AssessmentEntry getAssessmentEntry(CourseNode courseNode, Identity assessedIdentity) { return assessmentService.loadAssessmentEntry(assessedIdentity, cgm.getCourseEntry(), courseNode.getIdent()); } @Override public List<AssessmentEntry> getAssessmentEntries(Identity assessedIdentity) { return assessmentService.loadAssessmentEntriesByAssessedIdentity(assessedIdentity, cgm.getCourseEntry()); } @Override public List<AssessmentEntry> getAssessmentEntries(BusinessGroup assessedGoup, CourseNode courseNode) { return assessmentService.loadAssessmentEntries(assessedGoup, cgm.getCourseEntry(), courseNode.getIdent()); } @Override public AssessmentEntry createAssessmentEntry(CourseNode courseNode, Identity assessedIdentity, ScoreEvaluation scoreEvaluation) { RepositoryEntry referenceEntry = null; if(courseNode.needsReferenceToARepositoryEntry()) { referenceEntry = courseNode.getReferencedRepositoryEntry(); } Float score = null; Boolean passed = null; if(scoreEvaluation != null) { score = scoreEvaluation.getScore(); passed = scoreEvaluation.getPassed(); } return assessmentService .createAssessmentEntry(assessedIdentity, null, cgm.getCourseEntry(), courseNode.getIdent(), referenceEntry, score, passed); } @Override public AssessmentEntry updateAssessmentEntry(AssessmentEntry assessmentEntry) { return assessmentService.updateAssessmentEntry(assessmentEntry); } @Override public void saveNodeAttempts(CourseNode courseNode, Identity identity, Identity assessedIdentity, Integer attempts) { ICourse course = CourseFactory.loadCourse(cgm.getCourseEntry()); AssessmentEntry nodeAssessment = getOrCreate(assessedIdentity, courseNode); nodeAssessment.setAttempts(attempts); assessmentService.updateAssessmentEntry(nodeAssessment); //node log UserNodeAuditManager am = course.getCourseEnvironment().getAuditManager(); am.appendToUserNodeLog(courseNode, identity, assessedIdentity, "ATTEMPTS set to: " + String.valueOf(attempts)); // notify about changes AssessmentChangedEvent ace = new AssessmentChangedEvent(AssessmentChangedEvent.TYPE_ATTEMPTS_CHANGED, assessedIdentity); CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(ace, course); // user activity logging ThreadLocalUserActivityLogger.log(AssessmentLoggingAction.ASSESSMENT_ATTEMPTS_UPDATED, getClass(), LoggingResourceable.wrap(assessedIdentity), LoggingResourceable.wrapNonOlatResource(StringResourceableType.qtiAttempts, "", String.valueOf(attempts))); } @Override public void saveNodeComment(CourseNode courseNode, Identity identity, Identity assessedIdentity, String comment) { ICourse course = CourseFactory.loadCourse(cgm.getCourseEntry()); AssessmentEntry nodeAssessment = getOrCreate(assessedIdentity, courseNode); nodeAssessment.setComment(comment); assessmentService.updateAssessmentEntry(nodeAssessment); // node log UserNodeAuditManager am = course.getCourseEnvironment().getAuditManager(); am.appendToUserNodeLog(courseNode, identity, assessedIdentity, "COMMENT set to: " + comment); // notify about changes AssessmentChangedEvent ace = new AssessmentChangedEvent(AssessmentChangedEvent.TYPE_USER_COMMENT_CHANGED, assessedIdentity); CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(ace, course); // user activity logging ThreadLocalUserActivityLogger.log(AssessmentLoggingAction.ASSESSMENT_USERCOMMENT_UPDATED, getClass(), LoggingResourceable.wrap(assessedIdentity), LoggingResourceable.wrapNonOlatResource(StringResourceableType.qtiUserComment, "", StringHelper.stripLineBreaks(comment))); } @Override public void saveNodeCoachComment(CourseNode courseNode, Identity assessedIdentity, String comment) { ICourse course = CourseFactory.loadCourse(cgm.getCourseEntry()); AssessmentEntry nodeAssessment = getOrCreate(assessedIdentity, courseNode); nodeAssessment.setCoachComment(comment); assessmentService.updateAssessmentEntry(nodeAssessment); // notify about changes AssessmentChangedEvent ace = new AssessmentChangedEvent(AssessmentChangedEvent.TYPE_COACH_COMMENT_CHANGED, assessedIdentity); CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(ace, course); // user activity logging ThreadLocalUserActivityLogger.log(AssessmentLoggingAction.ASSESSMENT_COACHCOMMENT_UPDATED, getClass(), LoggingResourceable.wrap(assessedIdentity), LoggingResourceable.wrapNonOlatResource(StringResourceableType.qtiCoachComment, "", StringHelper.stripLineBreaks(comment))); } @Override public void incrementNodeAttempts(CourseNode courseNode, Identity assessedIdentity, UserCourseEnvironment userCourseEnv) { ICourse course = CourseFactory.loadCourse(cgm.getCourseEntry()); AssessmentEntry nodeAssessment = getOrCreate(assessedIdentity, courseNode); int attempts = nodeAssessment.getAttempts() == null ? 1 :nodeAssessment.getAttempts().intValue() + 1; nodeAssessment.setAttempts(attempts); assessmentService.updateAssessmentEntry(nodeAssessment); if(courseNode instanceof AssessableCourseNode) { // Update users efficiency statement efficiencyStatementManager.updateUserEfficiencyStatement(userCourseEnv); } // notify about changes AssessmentChangedEvent ace = new AssessmentChangedEvent(AssessmentChangedEvent.TYPE_ATTEMPTS_CHANGED, assessedIdentity); CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(ace, course); // user activity logging ThreadLocalUserActivityLogger.log(AssessmentLoggingAction.ASSESSMENT_ATTEMPTS_UPDATED, getClass(), LoggingResourceable.wrap(assessedIdentity), LoggingResourceable.wrapNonOlatResource(StringResourceableType.qtiAttempts, "", String.valueOf(attempts))); } @Override public void incrementNodeAttemptsInBackground(CourseNode courseNode, Identity assessedIdentity, UserCourseEnvironment userCourseEnv) { ICourse course = CourseFactory.loadCourse(cgm.getCourseEntry()); AssessmentEntry nodeAssessment = getOrCreate(assessedIdentity, courseNode); int attempts = nodeAssessment.getAttempts() == null ? 1 :nodeAssessment.getAttempts().intValue() + 1; nodeAssessment.setAttempts(attempts); assessmentService.updateAssessmentEntry(nodeAssessment); if(courseNode instanceof AssessableCourseNode) { // Update users efficiency statement efficiencyStatementManager.updateUserEfficiencyStatement(userCourseEnv); } // notify about changes AssessmentChangedEvent ace = new AssessmentChangedEvent(AssessmentChangedEvent.TYPE_ATTEMPTS_CHANGED, assessedIdentity); CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(ace, course); } @Override public void saveScoreEvaluation(AssessableCourseNode courseNode, Identity identity, Identity assessedIdentity, ScoreEvaluation scoreEvaluation, UserCourseEnvironment userCourseEnv, boolean incrementUserAttempts) { final ICourse course = CourseFactory.loadCourse(cgm.getCourseEntry()); final CourseEnvironment courseEnv = userCourseEnv.getCourseEnvironment(); Float score = scoreEvaluation.getScore(); Boolean passed = scoreEvaluation.getPassed(); Long assessmentId = scoreEvaluation.getAssessmentID(); String subIdent = courseNode.getIdent(); RepositoryEntry referenceEntry = courseNode.getReferencedRepositoryEntry(); AssessmentEntry assessmentEntry = getOrCreate(assessedIdentity, subIdent, referenceEntry); if(referenceEntry != null && !referenceEntry.equals(assessmentEntry.getReferenceEntry())) { assessmentEntry.setReferenceEntry(referenceEntry); } if(score == null) { assessmentEntry.setScore(null); } else { assessmentEntry.setScore(new BigDecimal(Float.toString(score))); } assessmentEntry.setPassed(passed); assessmentEntry.setFullyAssessed(scoreEvaluation.getFullyAssessed()); if(assessmentId != null) { assessmentEntry.setAssessmentId(assessmentId); } if(scoreEvaluation.getAssessmentStatus() != null) { assessmentEntry.setAssessmentStatus(scoreEvaluation.getAssessmentStatus()); } if(scoreEvaluation.getUserVisible() != null) { assessmentEntry.setUserVisibility(scoreEvaluation.getUserVisible()); } Integer attempts = null; if(incrementUserAttempts) { attempts = assessmentEntry.getAttempts() == null ? 1 :assessmentEntry.getAttempts().intValue() + 1; assessmentEntry.setAttempts(attempts); } assessmentEntry = assessmentService.updateAssessmentEntry(assessmentEntry); DBFactory.getInstance().commit();//commit before sending events //reevalute the tree ScoreAccounting scoreAccounting = userCourseEnv.getScoreAccounting(); scoreAccounting.evaluateAll(true); DBFactory.getInstance().commit();//commit before sending events // node log UserNodeAuditManager am = courseEnv.getAuditManager(); am.appendToUserNodeLog(courseNode, identity, assessedIdentity, "score set to: " + String.valueOf(scoreEvaluation.getScore())); if(scoreEvaluation.getPassed()!=null) { am.appendToUserNodeLog(courseNode, identity, assessedIdentity, "passed set to: " + scoreEvaluation.getPassed().toString()); } else { am.appendToUserNodeLog(courseNode, identity, assessedIdentity, "passed set to \"undefined\""); } if(scoreEvaluation.getAssessmentID()!=null) { am.appendToUserNodeLog(courseNode, assessedIdentity, assessedIdentity, "assessmentId set to: " + scoreEvaluation.getAssessmentID().toString()); } // notify about changes AssessmentChangedEvent ace = new AssessmentChangedEvent(AssessmentChangedEvent.TYPE_SCORE_EVAL_CHANGED, assessedIdentity); CoordinatorManager.getInstance().getCoordinator().getEventBus().fireEventToListenersOf(ace, course); // user activity logging if (scoreEvaluation.getScore()!=null) { ThreadLocalUserActivityLogger.log(AssessmentLoggingAction.ASSESSMENT_SCORE_UPDATED, getClass(), LoggingResourceable.wrap(assessedIdentity), LoggingResourceable.wrapNonOlatResource(StringResourceableType.qtiScore, "", String.valueOf(scoreEvaluation.getScore()))); } if (scoreEvaluation.getPassed()!=null) { ThreadLocalUserActivityLogger.log(AssessmentLoggingAction.ASSESSMENT_PASSED_UPDATED, getClass(), LoggingResourceable.wrap(assessedIdentity), LoggingResourceable.wrapNonOlatResource(StringResourceableType.qtiPassed, "", String.valueOf(scoreEvaluation.getPassed()))); } else { ThreadLocalUserActivityLogger.log(AssessmentLoggingAction.ASSESSMENT_PASSED_UPDATED, getClass(), LoggingResourceable.wrap(assessedIdentity), LoggingResourceable.wrapNonOlatResource(StringResourceableType.qtiPassed, "", "undefined")); } if (incrementUserAttempts && attempts!=null) { ThreadLocalUserActivityLogger.log(AssessmentLoggingAction.ASSESSMENT_ATTEMPTS_UPDATED, getClass(), LoggingResourceable.wrap(identity), LoggingResourceable.wrapNonOlatResource(StringResourceableType.qtiAttempts, "", String.valueOf(attempts))); } // write only when enabled for this course if (courseEnv.getCourseConfig().isEfficencyStatementEnabled()) { List<AssessmentNodeData> data = new ArrayList<AssessmentNodeData>(50); AssessmentHelper.getAssessmentNodeDataList(0, courseEnv.getRunStructure().getRootNode(), scoreAccounting, userCourseEnv, true, true, true, data); efficiencyStatementManager.updateUserEfficiencyStatement(assessedIdentity, courseEnv, data, cgm.getCourseEntry()); } if(course.getCourseConfig().isAutomaticCertificationEnabled()) { CourseNode rootNode = courseEnv.getRunStructure().getRootNode(); ScoreEvaluation rootEval = scoreAccounting.evalCourseNode((AssessableCourseNode)rootNode); if(rootEval != null && rootEval.getPassed() != null && rootEval.getPassed().booleanValue() && certificatesManager.isCertificationAllowed(assessedIdentity, cgm.getCourseEntry())) { CertificateTemplate template = null; Long templateId = course.getCourseConfig().getCertificateTemplate(); if(templateId != null) { template = certificatesManager.getTemplateById(templateId); } CertificateInfos certificateInfos = new CertificateInfos(assessedIdentity, rootEval.getScore(), rootEval.getPassed()); certificatesManager.generateCertificate(certificateInfos, cgm.getCourseEntry(), template, true); } } } @Override public Float getNodeScore(CourseNode courseNode, Identity identity) { if (courseNode == null) { return FLOAT_ZERO; // return default value } AssessmentEntry entry = assessmentService.loadAssessmentEntry(identity, cgm.getCourseEntry(), courseNode.getIdent()); if(entry != null && entry.getScore() != null) { return entry.getScore().floatValue(); } return FLOAT_ZERO; } @Override public String getNodeComment(CourseNode courseNode, Identity identity) { AssessmentEntry entry = assessmentService .loadAssessmentEntry(identity, cgm.getCourseEntry(), courseNode.getIdent()); return entry == null ? null : entry.getComment(); } @Override public String getNodeCoachComment(CourseNode courseNode, Identity identity) { AssessmentEntry entry = assessmentService .loadAssessmentEntry(identity, cgm.getCourseEntry(), courseNode.getIdent()); return entry == null ? null : entry.getCoachComment(); } @Override public Boolean getNodePassed(CourseNode courseNode, Identity identity) { if (courseNode == null) { return Boolean.FALSE; // return default value } AssessmentEntry nodeAssessment = assessmentService .loadAssessmentEntry(identity, cgm.getCourseEntry(), courseNode.getIdent()); return nodeAssessment == null ? null : nodeAssessment.getPassed(); } @Override public Integer getNodeAttempts(CourseNode courseNode, Identity identity) { if(courseNode == null) return INTEGER_ZERO; AssessmentEntry nodeAssessment = assessmentService .loadAssessmentEntry(identity, cgm.getCourseEntry(), courseNode.getIdent()); return nodeAssessment == null || nodeAssessment.getAttempts() == null ? INTEGER_ZERO : nodeAssessment.getAttempts(); } @Override public Long getAssessmentID(CourseNode courseNode, Identity identity) { AssessmentEntry nodeAssessment = assessmentService .loadAssessmentEntry(identity, cgm.getCourseEntry(), courseNode.getIdent()); return nodeAssessment == null ? null : nodeAssessment.getAssessmentId(); } @Override public Date getScoreLastModifiedDate(CourseNode courseNode, Identity identity) { if(courseNode == null) return null; AssessmentEntry nodeAssessment = assessmentService .loadAssessmentEntry(identity, cgm.getCourseEntry(), courseNode.getIdent()); return nodeAssessment == null ? null : nodeAssessment.getLastModified(); } @Override public Boolean getNodeFullyAssessed(CourseNode courseNode, Identity identity) { AssessmentEntry nodeAssessment = assessmentService .loadAssessmentEntry(identity, cgm.getCourseEntry(), courseNode.getIdent()); return nodeAssessment == null ? null : nodeAssessment.getFullyAssessed(); } @Override public OLATResourceable createOLATResourceableForLocking(Identity assessedIdentity) { return OresHelper.createOLATResourceableInstance("AssessmentManager::Identity", assessedIdentity.getKey()); } @Override public void registerForAssessmentChangeEvents(GenericEventListener gel, Identity identity) { CoordinatorManager.getInstance().getCoordinator().getEventBus().registerFor(gel, identity, cgm.getCourseEntry().getOlatResource()); } @Override public void deregisterFromAssessmentChangeEvents(GenericEventListener gel) { CoordinatorManager.getInstance().getCoordinator().getEventBus().deregisterFor(gel, cgm.getCourseEntry().getOlatResource()); } }