Java tutorial
/********************************************************************************** * $URL$ * $Id$ *********************************************************************************** * * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Etudes, Inc. * * Portions completed before September 1, 2008 * Copyright (c) 2007, 2008 The Regents of the University of Michigan & Foothill College, ETUDES Project * * Licensed under the Apache 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.apache.org/licenses/LICENSE-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.etudes.mneme.impl; import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.GregorianCalendar; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipOutputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.etudes.mneme.api.Assessment; import org.etudes.mneme.api.AssessmentPermissionException; import org.etudes.mneme.api.AssessmentPolicyException; import org.etudes.mneme.api.AssessmentService; import org.etudes.mneme.api.AssessmentType; import org.etudes.mneme.api.AttachmentService; import org.etudes.mneme.api.ExportQtiService; import org.etudes.mneme.api.GradesRejectsAssessmentException; import org.etudes.mneme.api.GradesService; import org.etudes.mneme.api.MnemeService; import org.etudes.mneme.api.Part; import org.etudes.mneme.api.PartDetail; import org.etudes.mneme.api.Pool; import org.etudes.mneme.api.PoolService; import org.etudes.mneme.api.Question; import org.etudes.mneme.api.QuestionService; import org.etudes.mneme.api.ReviewTiming; import org.etudes.mneme.api.SecurityService; import org.etudes.mneme.api.Settings; import org.etudes.mneme.api.SubmissionService; import org.etudes.util.api.Translation; import org.sakaiproject.db.api.SqlService; import org.sakaiproject.event.api.EventTrackingService; import org.sakaiproject.memory.api.Cache; import org.sakaiproject.thread_local.api.ThreadLocalManager; import org.sakaiproject.tool.api.SessionManager; import org.sakaiproject.user.api.User; import org.sakaiproject.user.api.UserDirectoryService; /** * AssessmentServiceImpl implements AssessmentService. */ public class AssessmentServiceImpl implements AssessmentService { /** Our logger. */ private static Log M_log = LogFactory.getLog(AssessmentServiceImpl.class); /** A cache of assessments. */ protected Cache assessmentCache = null; /** Dependency: AttachmentService */ protected AttachmentService attachmentService = null; /** Dependency: EventTrackingService */ protected EventTrackingService eventTrackingService = null; /** Dependency: ExportQtiService */ protected ExportQtiServiceImpl exportService = null; /** Dependency: GradesService */ protected GradesService gradesService = null; /** Dependency: PoolService */ protected PoolService poolService = null; /** Dependency: QuestionService */ protected QuestionService questionService = null; /** Dependency: SecurityService */ protected SecurityService securityService = null; /** Dependency: SessionManager */ protected SessionManager sessionManager = null; /** Dependency: SqlService */ protected SqlService sqlService = null; /** Storage handler. */ protected AssessmentStorage storage = null; /** Storage option map key for the option to use. */ protected String storageKey = null; /** Map of registered PoolStorage options. */ protected Map<String, AssessmentStorage> storgeOptions; /** Dependency: SubmissionService */ protected SubmissionServiceImpl submissionService = null; /** Dependency: ThreadLocalManager. */ protected ThreadLocalManager threadLocalManager = null; protected UserDirectoryService userDirectoryService = null; /** * {@inheritDoc} */ public Boolean allowEditAssessment(Assessment assessment) { if (assessment == null) throw new IllegalArgumentException(); String userId = sessionManager.getCurrentSessionUserId(); if (M_log.isDebugEnabled()) M_log.debug("allowEditAssessment: " + assessment.getId() + ": " + userId); // check permission - user must have MANAGE_PERMISSION in the context boolean ok = securityService.checkSecurity(userId, MnemeService.MANAGE_PERMISSION, assessment.getContext()); return ok; } /** * {@inheritDoc} */ public Boolean allowGuest(String context) { if (context == null) throw new IllegalArgumentException(); String userId = sessionManager.getCurrentSessionUserId(); if (M_log.isDebugEnabled()) M_log.debug("allowGuest: " + context + ": " + userId); // check permission - user must have GUEST_PERMISSION in the context boolean ok = securityService.checkSecurity(userId, MnemeService.GUEST_PERMISSION, context); return ok; } /** * {@inheritDoc} */ public Boolean allowListDeliveryAssessment(String context) { if (context == null) throw new IllegalArgumentException(); String userId = sessionManager.getCurrentSessionUserId(); if (M_log.isDebugEnabled()) M_log.debug("allowListDeliveryAssessment: " + context + ": " + userId); // check permission - user must have SUBMIT_PERMISSION or MANAGE in the context boolean ok = securityService.checkSecurity(userId, MnemeService.SUBMIT_PERMISSION, context) || securityService.checkSecurity(userId, MnemeService.MANAGE_PERMISSION, context); return Boolean.valueOf(ok); } /** * {@inheritDoc} */ public Boolean allowManageAssessments(String context) { if (context == null) throw new IllegalArgumentException(); String userId = sessionManager.getCurrentSessionUserId(); if (M_log.isDebugEnabled()) M_log.debug("allowManageAssessments: " + context + ": " + userId); // check permission - user must have MANAGE_PERMISSION in the context boolean ok = securityService.checkSecurity(userId, MnemeService.MANAGE_PERMISSION, context); return ok; } /** * {@inheritDoc} */ public Boolean allowRemoveAssessment(Assessment assessment) { if (assessment == null) throw new IllegalArgumentException(); if (M_log.isDebugEnabled()) M_log.debug("allowRemoveAssessment: " + assessment.getId()); // user must have manage permission if (!this.allowManageAssessments(assessment.getContext())) return Boolean.FALSE; // check policy return satisfyAssessmentRemovalPolicy(assessment); } /** * {@inheritDoc} */ public Boolean allowSetFormalCourseEvaluation(String context) { if (context == null) throw new IllegalArgumentException(); String userId = sessionManager.getCurrentSessionUserId(); if (M_log.isDebugEnabled()) M_log.debug("allowSetFormalCourseEvaluation: " + context + ": " + userId); // check permission - user must have COURSE_EVAL_PERMISSION in the context boolean ok = securityService.checkSecurity(userId, MnemeService.COURSE_EVAL_PERMISSION, context); return ok; } /** * {@inheritDoc} */ public void applyBaseDateTx(String context, int days) { if (context == null) throw new IllegalArgumentException("applyBaseDateTx: context is null"); if (days == 0) return; try { // security check securityService.secure(sessionManager.getCurrentSessionUserId(), MnemeService.MANAGE_PERMISSION, context); // do this the slow way (i.e. not all in SQL) to avoid the y2038 bug and assure proper gradebook integration // see Etudes Jira MN-1125 // get all assessments List<Assessment> assessments = getContextAssessments(context, AssessmentsSort.odate_a, Boolean.FALSE); GregorianCalendar gc = new GregorianCalendar(); // for each one, apply the base date change for (Assessment assessment : assessments) { if (assessment.getDates().getAcceptUntilDate() != null) { gc.setTime(assessment.getDates().getAcceptUntilDate()); gc.add(Calendar.DATE, days); assessment.getDates().setAcceptUntilDate(gc.getTime()); } if (assessment.getDates().getDueDate() != null) { gc.setTime(assessment.getDates().getDueDate()); gc.add(Calendar.DATE, days); assessment.getDates().setDueDate(gc.getTime()); } if (assessment.getDates().getOpenDate() != null) { gc.setTime(assessment.getDates().getOpenDate()); gc.add(Calendar.DATE, days); assessment.getDates().setOpenDate(gc.getTime()); } if (assessment.getReview().getDate() != null) { gc.setTime(assessment.getReview().getDate()); gc.add(Calendar.DATE, days); assessment.getReview().setDate(gc.getTime()); } // save try { saveAssessment(assessment); } catch (AssessmentPermissionException e) { M_log.warn("applyBaseDateTx: " + assessment.getId() + " exception: " + e.toString()); } catch (AssessmentPolicyException e) { M_log.warn("applyBaseDateTx: " + assessment.getId() + " exception: " + e.toString()); } } } catch (AssessmentPermissionException e) { throw new RuntimeException("applyBaseDateTx: security check failed: " + e.toString()); } } /** * {@inheritDoc} */ public void clearStaleMintAssessments() { // give it a day Date stale = new Date(); stale.setTime(stale.getTime() - (1000l * 60l * 60l * 24l)); if (M_log.isDebugEnabled()) M_log.debug("clearStaleMintAssessments"); List<String> ids = this.storage.clearStaleMintAssessments(stale); // events for (String id : ids) { eventTrackingService.post(eventTrackingService.newEvent(MnemeService.ASSESSMENT_DELETE, getAssessmentReference(id), true)); } } /** * {@inheritDoc} */ public Assessment copyAssessment(String context, Assessment assessment) throws AssessmentPermissionException { if (context == null) throw new IllegalArgumentException(); if (assessment == null) throw new IllegalArgumentException(); // security check securityService.secure(sessionManager.getCurrentSessionUserId(), MnemeService.MANAGE_PERMISSION, context); AssessmentImpl rv = doCopyAssessment(context, assessment, null, null, true, null); return rv; } /** * {@inheritDoc} */ public Integer countAssessments(String context) { if (context == null) throw new IllegalArgumentException(); if (M_log.isDebugEnabled()) M_log.debug("countAssessments: " + context); return this.storage.countAssessments(context); } /** * Returns to uninitialized state. */ public void destroy() { M_log.info("destroy()"); } /** * {@inheritDoc} */ public void exportAssessments(String context, String[] ids, ZipOutputStream zip) throws AssessmentPermissionException, IOException { this.exportService.exportAssessments(context, ids, zip); } /** * {@inheritDoc} */ public List<Assessment> getArchivedAssessments(String context) { if (context == null) throw new IllegalArgumentException(); if (M_log.isDebugEnabled()) M_log.debug("getArchivedAssessments: " + context); List<Assessment> rv = new ArrayList<Assessment>(this.storage.getArchivedAssessments(context)); return rv; } /** * {@inheritDoc} */ public Assessment getAssessment(String id) { if (id == null) throw new IllegalArgumentException(); // for thread-local caching String key = cacheKey(id); AssessmentImpl rv = (AssessmentImpl) this.threadLocalManager.get(key); if (rv != null) { // return a copy return this.storage.clone(rv); } if (M_log.isDebugEnabled()) M_log.debug("getAssessment: " + id); rv = this.storage.getAssessment(id); // thread-local cache (a copy) if (rv != null) this.threadLocalManager.set(key, this.storage.clone(rv)); return rv; } /** * {@inheritDoc} */ public List<Assessment> getAssessmentsNeedingResultsEmail() { List<Assessment> rv = new ArrayList<Assessment>(); // this gets the candidates - but does not check the close dates List<AssessmentImpl> assessments = this.storage.getAssessmentsNeedingResultsEmail(); // TODO: security? // filter in those that are closed now, and filter out any that are surveys but not formal course evaluations for (Assessment a : assessments) { // surveys that are not formal evals do not support this feature //if ((a.getType() == AssessmentType.survey) && (!a.getFormalCourseEval())) continue; //surveys that are not formal evals need to have view results clicked to support this feature if ((a.getType() == AssessmentType.survey) && (!a.getFormalCourseEval()) && !a.getFrozen()) continue; if (a.getDates().getIsClosed()) { rv.add(a); } } return rv; } /** * {@inheritDoc} */ public List<Assessment> getContextAssessments(String context, AssessmentsSort sort, Boolean publishedOnly) { if (context == null) throw new IllegalArgumentException(); if (publishedOnly == null) throw new IllegalArgumentException(); if (sort == null) sort = AssessmentsSort.cdate_a; if (M_log.isDebugEnabled()) M_log.debug("getContextAssessments: " + context + " sort: " + sort + " publishOnly: " + publishedOnly); List<Assessment> rv = new ArrayList<Assessment>( this.storage.getContextAssessments(context, sort, publishedOnly)); // TODO: needed? // // thread-local cache each found assessment // for (Assessment assessment : rv) // { // String key = cacheKey(assessment.getId()); // this.threadLocalManager.set(key, this.storage.newAssessment((AssessmentImpl) assessment)); // } return rv; } /** * {@inheritDoc} */ public List<Assessment> getFormalEvaluationsNeedingNotification() { List<Assessment> rv = new ArrayList<Assessment>(); // this gets the candidates - but does not check the close dates List<Assessment> assessments = new ArrayList<Assessment>( this.storage.getFormalEvaluationsNeedingNotification()); // TODO: security? return assessments; } /** * {@inheritDoc} */ public Date getMaxStartDate(String context) { Date maxDate = this.storage.getMaxStartDate(context); return maxDate; } /** * {@inheritDoc} */ public Date getMinStartDate(String context) { Date minDate = this.storage.getMinStartDate(context); return minDate; } /** * {@inheritDoc} */ public List<User> getSubmitUsers(String context) { if (M_log.isDebugEnabled()) M_log.debug("getSubmitUsers: " + context); // get the ids Set<String> ids = this.securityService.getUsersIsAllowed(MnemeService.SUBMIT_PERMISSION, context); // turn into users List<User> users = this.userDirectoryService.getUsers(ids); // sort - by user sort name Collections.sort(users, new Comparator() { public int compare(Object arg0, Object arg1) { int rv = ((User) arg0).getSortName().compareToIgnoreCase(((User) arg1).getSortName()); return rv; } }); return users; } /** * Final initialization, once all dependencies are set. */ public void init() { try { // storage - as configured if (this.storageKey != null) { // if set to "SQL", replace with the current SQL vendor if ("SQL".equals(this.storageKey)) { this.storageKey = sqlService.getVendor(); } this.storage = this.storgeOptions.get(this.storageKey); } // use "default" if needed if (this.storage == null) { this.storage = this.storgeOptions.get("default"); } if (storage == null) M_log.warn("no storage set: " + this.storageKey); storage.init(); M_log.info("init(): storage: " + this.storage); } catch (Throwable t) { M_log.warn("init(): ", t); } } /** * {@inheritDoc} */ public Assessment newAssessment(String context) throws AssessmentPermissionException { if (context == null) throw new IllegalArgumentException(); if (M_log.isDebugEnabled()) M_log.debug("newAssessment: " + context); // security check securityService.secure(sessionManager.getCurrentSessionUserId(), MnemeService.MANAGE_PERMISSION, context); AssessmentImpl rv = this.storage.newAssessment(); rv.setContext(context); // if we have a gradebook, enable gb integration if (this.gradesService.available(context)) { rv.getGrading().setGradebookIntegration(Boolean.TRUE); } save(rv); return rv; } /** * {@inheritDoc} */ public Assessment newEmptyAssessment(String context) { if (M_log.isDebugEnabled()) M_log.debug("newEmptyAssessment: "); AssessmentImpl rv = this.storage.newAssessment(); rv.setContext(context); return rv; } public Settings newEmptySettings() { if (M_log.isDebugEnabled()) M_log.debug("newEmptySettings: "); SettingsImpl rv = new SettingsImpl(); return (Settings) rv; } /** * {@inheritDoc} */ public void removeAssessment(Assessment assessment) throws AssessmentPermissionException, AssessmentPolicyException { if (assessment == null) throw new IllegalArgumentException(); if (M_log.isDebugEnabled()) M_log.debug("removeAssessment: " + assessment.getId()); // security check securityService.secure(sessionManager.getCurrentSessionUserId(), MnemeService.MANAGE_PERMISSION, assessment.getContext()); // policy check if (!satisfyAssessmentRemovalPolicy(assessment)) throw new AssessmentPolicyException(); // clear any test-drive submissions for this assessment this.submissionService.removeTestDriveSubmissions(assessment); // clear the cache String key = cacheKey(assessment.getId()); this.threadLocalManager.set(key, null); // retract the test from the gb if (assessment.getIsValid() && assessment.getGrading().getGradebookIntegration() && assessment.getPublished()) { this.gradesService.retractAssessmentGrades(assessment); } this.storage.removeAssessment((AssessmentImpl) assessment); // event eventTrackingService.post(eventTrackingService.newEvent(MnemeService.ASSESSMENT_DELETE, getAssessmentReference(assessment.getId()), true)); } /** * {@inheritDoc} */ public void rescoreAssessment(Assessment assessment) throws AssessmentPermissionException, GradesRejectsAssessmentException { // secure this.securityService.secure(this.sessionManager.getCurrentSessionUserId(), MnemeService.MANAGE_PERMISSION, assessment.getContext()); // ignore if not locked if (!assessment.getIsLocked()) return; // pull the assessment from the grading authority if (assessment.getGradebookIntegration() && assessment.getPublished()) { this.gradesService.retractAssessmentGrades(assessment); } // re-score this.submissionService.rescoreSubmission(assessment); // return to the grading authority if (assessment.getIsValid() && assessment.getGradebookIntegration() && assessment.getPublished()) { // we should not be in the gb! if (this.gradesService.assessmentReported(assessment)) { throw new GradesRejectsAssessmentException(); } // try to get into the gb this.gradesService.initAssessmentGrades(assessment); // report any completed official submissions this.gradesService.reportAssessmentGrades(assessment); } } /** * {@inheritDoc} */ public void saveAssessment(Assessment assessment) throws AssessmentPermissionException, AssessmentPolicyException { if (assessment == null) throw new IllegalArgumentException(); if (assessment.getId() == null) throw new IllegalArgumentException(); // check for empty special access ((AssessmentSpecialAccessImpl) assessment.getSpecialAccess()).consolidate(); // if the type is changed to assignment, enforce related settings changes if ((((AssessmentImpl) assessment).getTypeChanged()) && (assessment.getType() == AssessmentType.assignment)) { // assignments always are flexible assessment.setRandomAccess(Boolean.TRUE); // also default to "review available upon submission" and "manual release" assessment.getReview().setTiming(ReviewTiming.submitted); assessment.getGrading().setAutoRelease(Boolean.FALSE); } // if type is changed from survey, clear formal evaluation if ((((AssessmentImpl) assessment).getTypeChanged()) && (assessment.getType() != AssessmentType.survey)) { // formal course evaluation is only for surveys assessment.setFormalCourseEval(Boolean.FALSE); assessment.setNotifyEval(Boolean.FALSE); } // enforce 1 try for formal course evaluation if (assessment.getFormalCourseEval()) { assessment.setTries(1); } // enforce surveys that are not formal evals cannot have results email /*if ((assessment.getType() == AssessmentType.survey) && (!assessment.getFormalCourseEval())) { assessment.setResultsEmail(null); }*/ // if any changes made, clear mint if (assessment.getIsChanged()) { ((AssessmentImpl) assessment).clearMint(); } // otherwise we don't save: but if mint, we delete else { // if mint, delete instead of save if (((AssessmentImpl) assessment).getMint()) { if (M_log.isDebugEnabled()) M_log.debug("saveAssessment: deleting mint: " + assessment.getId()); // clear the cache this.threadLocalManager.set(cacheKey(assessment.getId()), null); this.storage.removeAssessment((AssessmentImpl) assessment); // event eventTrackingService.post(eventTrackingService.newEvent(MnemeService.ASSESSMENT_DELETE, getAssessmentReference(assessment.getId()), true)); } return; } if (M_log.isDebugEnabled()) M_log.debug("saveAssessment: " + assessment.getId()); // security check securityService.secure(sessionManager.getCurrentSessionUserId(), MnemeService.MANAGE_PERMISSION, assessment.getContext()); // check for changes not allowed if locked if ((assessment.getIsLocked()) && ((AssessmentImpl) assessment).getIsLockedChanged()) throw new AssessmentPolicyException(); // clear any test-drive submissions for this assessment this.submissionService.removeTestDriveSubmissions(assessment); // see if we need to retract or release grades boolean retract = false; boolean release = false; if (((AssessmentGradingImpl) (assessment.getGrading())).getAutoReleaseChanged()) { if (assessment.getGrading().getAutoRelease()) { release = true; } else { retract = true; } } // clear the auto-release change tracking ((AssessmentGradingImpl) (assessment.getGrading())) .initAutoRelease(assessment.getGrading().getAutoRelease()); // see if we have had a title change (and clear) boolean titleChanged = ((AssessmentImpl) assessment).getTitleChanged(); ((AssessmentImpl) assessment).initTitle(assessment.getTitle()); // see if we had a change in published (and clear) boolean publishedChanged = ((AssessmentImpl) assessment).getPublishedChanged(); ((AssessmentImpl) assessment).initPublished(assessment.getPublished()); // see if we have had a due date change (and clear) boolean dueChanged = ((AssessmentDatesImpl) assessment.getDates()).getDueDateChanged(); ((AssessmentDatesImpl) assessment.getDates()).initDueDate(assessment.getDates().getDueDate()); // see if we have just been archived (and clear) boolean archivedChanged = ((AssessmentImpl) assessment).getArchivedChanged(); ((AssessmentImpl) assessment).initArchived(assessment.getArchived()); // see if we have changed our gradebook integration (and clear) boolean gbIntegrationChanged = ((AssessmentGradingImpl) (assessment.getGrading())) .getGradebookIntegrationChanged(); ((AssessmentGradingImpl) (assessment.getGrading())) .initGradebookIntegration(assessment.getGrading().getGradebookIntegration()); // see if we need to re-score (and clear) boolean rescore = assessment.getIsLocked() && ((AssessmentImpl) assessment).getNeedsRescore(); ((AssessmentImpl) assessment).initNeedsRescore(false); // see if the type changed (and clear) boolean typeChanged = ((AssessmentImpl) assessment).getTypeChanged(); assessment.initType(assessment.getType()); // make sure we are not still considered invalid for gb - if we are, we will pick that up down below ((AssessmentGradingImpl) (assessment.getGrading())).initGradebookRejectedAssessment(Boolean.FALSE); // see if we have changed our validity boolean validityChanged = false; boolean nowValid = assessment.getIsValid(); Assessment b4 = getAssessment(assessment.getId()); if (b4 != null) { validityChanged = (b4.getIsValid().booleanValue() != nowValid); } // if we are just going published and not yet live, bring the assessment live if (!assessment.getIsLive() && publishedChanged && assessment.getPublished()) { ((AssessmentImpl) assessment).lock(); } // get the assessment before these changes Assessment current = getAssessment(assessment.getId()); // save the changes save((AssessmentImpl) assessment); // event for change in published if (publishedChanged) { if (assessment.getPublished()) { eventTrackingService.post(eventTrackingService.newEvent(MnemeService.ASSESSMENT_PUBLISH, getAssessmentReference(assessment.getId()), true)); } else { eventTrackingService.post(eventTrackingService.newEvent(MnemeService.ASSESSMENT_UNPUBLISH, getAssessmentReference(assessment.getId()), true)); } } // if the name or due date has changed, or we are retracting submissions, or we are now unpublished, // or we are now invalid, or we have just been archived, or we are now not gradebook integrated, // or we are releasing (we need to remove our entry so we can add it back without conflict) // or we changed type to survey // retract the assessment from the grades authority if (rescore || titleChanged || dueChanged || retract || release || (publishedChanged && !assessment.getPublished()) || (validityChanged && !nowValid) || (archivedChanged && assessment.getArchived()) || (gbIntegrationChanged && !assessment.getGradebookIntegration()) || (typeChanged && assessment.getType() == AssessmentType.survey)) { // retract the entire assessment from grades - use the old information (title) (if we existed before this call) // ONLY IF we were expecting to be in the gb based on current values if ((current != null) && current.getIsValid() && current.getGradebookIntegration() && current.getPublished()) { this.gradesService.retractAssessmentGrades(current); } // retract the submissions if (retract) { this.submissionService.retractSubmissions(assessment); } } // re-score the submissions if needed if (rescore) { this.submissionService.rescoreSubmission(assessment); } // if the name or due date has changed, or we are releasing submissions, or we are now published, // or we are now valid (and are published), or we are now gradebook integrated, // or we are retracting (we need to add the entry back in that we just removed) // report the assessment and all completed submissions to the grades authority if (rescore || titleChanged || dueChanged || release || retract || (publishedChanged && assessment.getPublished()) || (validityChanged && nowValid && assessment.getPublished()) || (gbIntegrationChanged && assessment.getGradebookIntegration())) { if (assessment.getIsValid() && assessment.getGradebookIntegration() && assessment.getPublished()) { try { // we should not be in the gb! if (this.gradesService.assessmentReported(assessment)) { throw new GradesRejectsAssessmentException(); } // try to get into the gb this.gradesService.initAssessmentGrades(assessment); // report any completed official submissions this.gradesService.reportAssessmentGrades(assessment); } catch (GradesRejectsAssessmentException e) { // mark as invalid ((AssessmentGradingImpl) (assessment.getGrading())) .initGradebookRejectedAssessment(Boolean.TRUE); // re-save save((AssessmentImpl) assessment); } } // release the submissions, if we need to (each will have the grade reported) if (release) { this.submissionService.releaseSubmissions(assessment, Boolean.FALSE); } } // our change might make other gradebook-invalid assessments valid - only if we were in the gb to stat with if ((current != null) && current.getPublished() && current.getGradebookIntegration() && (!current.getArchived()) && current.getIsValid()) { if (titleChanged || (publishedChanged && (!assessment.getPublished())) || (archivedChanged && assessment.getArchived()) || (gbIntegrationChanged && (!assessment.getGradebookIntegration()))) { // get all context assessments that are invalid due to gb integration List<AssessmentImpl> gbInvalid = this.storage .getContextGbInvalidAssessments(assessment.getContext()); // for each one for (AssessmentImpl a : gbInvalid) { // clear the invalid (so it does not trigger the getIsValid call) ((AssessmentGradingImpl) (a.getGrading())).initGradebookRejectedAssessment(Boolean.FALSE); if (a.getIsValid() && a.getGradebookIntegration() && a.getPublished()) { try { // we should not be in the gb! if (this.gradesService.assessmentReported(a)) { throw new GradesRejectsAssessmentException(); } // try to get into the gb this.gradesService.initAssessmentGrades(a); // report any completed official submissions this.gradesService.reportAssessmentGrades(a); // save (the invalid flag is cleared) save((AssessmentImpl) a); } catch (GradesRejectsAssessmentException e) { } } } } } } /** * {@inheritDoc} */ public void sendEvalNotification(Assessment assessment) { if (assessment == null) throw new IllegalArgumentException(); // do it - the submission service handles this ((SubmissionServiceImpl) this.submissionService).notifyStudentEvaluation(assessment); } /** * {@inheritDoc} */ public void sendResults(Assessment assessment) { if (assessment == null) throw new IllegalArgumentException(); // must be set for email, and be closed if (assessment.getResultsEmail() == null) return; if (!assessment.getDates().getIsClosed()) return; // do it - the submission service handles this ((SubmissionServiceImpl) this.submissionService).emailResults(assessment); } /** * Dependency: AttachmentService. * * @param service * The AttachmentService. */ public void setAttachmentService(AttachmentService service) { attachmentService = service; } /** * {@inheritDoc} */ public void setEvaluationSent(Assessment assessment, Date date) { if (assessment == null) throw new IllegalArgumentException(); if (date == null) throw new IllegalArgumentException(); // TODO: security? this.storage.setEvaluationSent(assessment.getId(), date); } /** * Dependency: EventTrackingService. * * @param service * The EventTrackingService. */ public void setEventTrackingService(EventTrackingService service) { eventTrackingService = service; } public void setExportService(ExportQtiServiceImpl exportService) { this.exportService = exportService; } /** * Dependency: GradesService. * * @param service * The GradesService. */ public void setGradesService(GradesService service) { this.gradesService = service; } /** * Dependency: PoolService. * * @param service * The PoolService. */ public void setPoolService(PoolService service) { poolService = service; } /** * Dependency: QuestionService. * * @param service * The QuestionService. */ public void setQuestionService(QuestionService service) { questionService = service; } /** * {@inheritDoc} */ public void setResultsSent(Assessment assessment, Date date) { if (assessment == null) throw new IllegalArgumentException(); if (date == null) throw new IllegalArgumentException(); // TODO: security? this.storage.setResultsSent(assessment.getId(), date); } /** * Dependency: SecurityService. * * @param service * The SecurityService. */ public void setSecurityService(SecurityService service) { securityService = service; } /** * Dependency: SessionManager. * * @param service * The SessionManager. */ public void setSessionManager(SessionManager service) { sessionManager = service; } /** * Dependency: SqlService. * * @param service * The SqlService. */ public void setSqlService(SqlService service) { sqlService = service; } /** * Set the storage class options. * * @param options * The PoolStorage options. */ public void setStorage(Map options) { this.storgeOptions = options; } /** * Set the storage option key to use, selecting which PoolStorage to use. * * @param key * The storage option key. */ public void setStorageKey(String key) { this.storageKey = key; } /** * Dependency: SubmissionService. * * @param service * The SubmissionService. */ public void setSubmissionService(SubmissionService service) { submissionService = (SubmissionServiceImpl) service; } /** * Dependency: ThreadLocalManager. * * @param service * The SqlService. */ public void setThreadLocalManager(ThreadLocalManager service) { threadLocalManager = service; } /** * Dependency: UserDirectoryService. * * @param service * The UserDirectoryService. */ public void setUserDirectoryService(UserDirectoryService service) { userDirectoryService = service; } /** * Form a key for caching an assessment. * * @param assessmentId * The assessment id. * @return The cache key. */ protected String cacheKey(String assessmentId) { String key = "mneme:assessment:" + assessmentId; return key; } /** * Copy an assessment * * @param context * The destination context. * @param assessment * The source assessment. * @param pidMap * A map (old pool id -> new pool id) to use to convert all pool references. * @param qidMap * A map (old question id -> new question id) to use to convert all question references. * @param appendTitle * if true, append text to the title, else leave the title an exact copy. * @param attachmentTranslations * A list of Translations for attachments and embedded media. */ protected AssessmentImpl doCopyAssessment(String context, Assessment assessment, Map<String, String> pidMap, Map<String, String> qidMap, boolean appendTitle, List<Translation> attachmentTranslations) { if (context == null) throw new IllegalArgumentException(); if (assessment == null) throw new IllegalArgumentException(); if (M_log.isDebugEnabled()) M_log.debug("doCopyAssessment: context:" + context + " id: " + assessment.getId()); String userId = sessionManager.getCurrentSessionUserId(); Date now = new Date(); AssessmentImpl rv = this.storage.clone((AssessmentImpl) assessment); // clear the id to make it a new one rv.id = null; // set the context rv.setContext(context); // add to the title if (appendTitle) { rv.setTitle(((PoolServiceImpl) this.poolService).addDate("copy-text", rv.getTitle(), now)); } // clear archived rv.initArchived(Boolean.FALSE); // clear out any special access rv.getSpecialAccess().clear(); // start out unpublished rv.initPublished(Boolean.FALSE); // clear frozen rv.initFrozen(Boolean.FALSE); // and not-live, non-locked rv.initLive(Boolean.FALSE); rv.initLocked(Boolean.FALSE); // email results not sent rv.initResultsSent(null); // newly copied are never formal course evaluation rv.initFormalCourseEval(Boolean.FALSE); rv.initNotifyEval(Boolean.FALSE); rv.initEvalSent(null); // surveys can't have auto-send results email /*if ((rv.getType() == AssessmentType.survey) && (!rv.getFormalCourseEval())) { rv.initResultsEmail(null); }*/ ((AssessmentGradingImpl) (rv.getGrading())).initGradebookRejectedAssessment(Boolean.FALSE); // update created and last modified information rv.getCreatedBy().setDate(now); rv.getCreatedBy().setUserId(userId); rv.getModifiedBy().setDate(now); rv.getModifiedBy().setUserId(userId); // set the parts to their original question and pool values for (Part part : rv.getParts().getParts()) { // if any detail fails to restore, remove it for (Iterator<PartDetail> i = part.getDetails().iterator(); i.hasNext();) { PartDetail detail = i.next(); if (!detail.restoreToOriginal(pidMap, qidMap)) { i.remove(); } } } // translate embedded media references if (attachmentTranslations != null) { rv.getPresentation().setText(this.attachmentService .translateEmbeddedReferences(rv.getPresentation().getText(), attachmentTranslations)); rv.getSubmitPresentation().setText(this.attachmentService .translateEmbeddedReferences(rv.getSubmitPresentation().getText(), attachmentTranslations)); for (Part p : rv.getParts().getParts()) { p.getPresentation().setText(this.attachmentService .translateEmbeddedReferences(p.getPresentation().getText(), attachmentTranslations)); } } // change the auto-pool to the imported version of the pool if (rv.poolId != null) { // if we have pool translations, see if we can find our auto-pool in the new set (would happen on an import assessment from site) if (pidMap != null) { String translated = pidMap.get(rv.poolId); if (translated != null) { rv.poolId = translated; } else { rv.poolId = null; } } // otherwise just clear our auto-pool (would happen on a copy assessment) else { rv.poolId = null; } } // save this.storage.saveAssessment(rv); // event eventTrackingService.post(eventTrackingService.newEvent(MnemeService.ASSESSMENT_NEW, getAssessmentReference(rv.getId()), true)); return rv; } /** * Form an assessment reference for this assessment id. * * @param assessmentId * the assessment id. * @return the assessment reference for this assessment id. */ protected String getAssessmentReference(String assessmentId) { String ref = MnemeService.REFERENCE_ROOT + "/" + MnemeService.ASSESSMENT_TYPE + "/" + assessmentId; return ref; } /** * Set this assessment to be live. * * @param assessment * The assessment. */ protected void makeLive(Assessment assessment) { // clear the cache this.threadLocalManager.set(cacheKey(assessment.getId()), null); this.storage.makeLive(assessment); } /** * Remove any draw dependencies on this pool from all unlocked assessments. * * @param question * The question. */ protected void removeDependency(Pool pool) { // clear any test-drive submissions for this assessment this.submissionService.removeTestDriveSubmissions(pool.getContext()); this.storage.removeDependency(pool); } /** * Remove any pick dependencies on this question from all unlocked assessments. * * @param question * The question. */ protected void removeDependency(Question question) { // clear any test-drive submissions for this assessment this.submissionService.removeTestDriveSubmissions(question.getContext()); this.storage.removeDependency(question); } /** * Check if this assessment meets the delete policy. * * @param assessment * The assessment. * @return TRUE if the assessment may be deleted, FALSE if not. */ protected Boolean satisfyAssessmentRemovalPolicy(Assessment assessment) { // live tests may not be deleted if (assessment.getIsLive()) return Boolean.FALSE; return Boolean.TRUE; } /** * Save the assessment * * @param assessment * The assessment. */ protected void save(AssessmentImpl assessment) { if (M_log.isDebugEnabled()) M_log.debug("save: " + assessment.getId()); Date now = new Date(); String userId = sessionManager.getCurrentSessionUserId(); String event = MnemeService.ASSESSMENT_EDIT; // if the assessment is new (i.e. no id), set the createdBy information, if not already set if (assessment.getId() == null) { if (assessment.getCreatedBy().getUserId() == null) { assessment.getCreatedBy().setDate(now); assessment.getCreatedBy().setUserId(userId); } event = MnemeService.ASSESSMENT_NEW; } // update last modified information assessment.getModifiedBy().setDate(now); assessment.getModifiedBy().setUserId(userId); // clear the cache this.threadLocalManager.set(cacheKey(assessment.getId()), null); // save this.storage.saveAssessment(assessment); // event eventTrackingService .post(eventTrackingService.newEvent(event, getAssessmentReference(assessment.getId()), true)); } }