Index: lams_central/conf/language/lams/ApplicationResources.properties =================================================================== diff -u -ra703dc22dba0e0bd9da0c71fb843626f506f3722 -rf4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a --- lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision a703dc22dba0e0bd9da0c71fb843626f506f3722) +++ lams_central/conf/language/lams/ApplicationResources.properties (.../ApplicationResources.properties) (revision f4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a) @@ -841,6 +841,7 @@ authoring.label.grouping.learners.choice = Learner's Choice authoring.label.grouping.teachers.choice = Teacher's Choice authoring.label.grouping.random.allocation = Random Allocation +authoring.label.question.collection = Put new questions into following Question Bank collection authoring.label.numgroups = Number of teams: authoring.error.numgroups = Number of teams must be 1 to 99. authoring.label.numlearners = Number of learners: @@ -1144,4 +1145,4 @@ label.branching.general.instructions = Place the lesson participants in their branches. Initially you can add and remove learners, but once a participant starts one of the branches then you will not be able to remove learners from any branches. If you try to remove someone from a branch and they will not remove then check their progress - if they start using the branch while you are on this screen you will not get any errors but you will not be able to remove them from the branch. You will still be able to add learners to branches. label.grouping.popup.viewmode.message = You are presently in group view mode. Groups can not be modified. authoring.tbl.shuffle.questions = Shuffle question order in iRAT -authoring.tbl.shuffle.questions.tooltip = Shuffles the order of questions in the iRAT for all students. In the tRAT the order of question will be the same for all students. +authoring.tbl.shuffle.questions.tooltip = Shuffles the order of questions in the iRAT for all students. In the tRAT the order of question will be the same for all students. \ No newline at end of file Index: lams_central/src/java/org/lamsfoundation/lams/authoring/service/AuthoringService.java =================================================================== diff -u -r8a6ef31e47f040438cce78848c3ccb238a9620b7 -rf4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a --- lams_central/src/java/org/lamsfoundation/lams/authoring/service/AuthoringService.java (.../AuthoringService.java) (revision 8a6ef31e47f040438cce78848c3ccb238a9620b7) +++ lams_central/src/java/org/lamsfoundation/lams/authoring/service/AuthoringService.java (.../AuthoringService.java) (revision f4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a) @@ -23,59 +23,18 @@ package org.lamsfoundation.lams.authoring.service; -import java.io.File; -import java.io.IOException; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedMap; -import java.util.Vector; - -import javax.servlet.http.HttpSession; - +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.lamsfoundation.lams.authoring.IObjectExtractor; import org.lamsfoundation.lams.authoring.ObjectExtractorException; import org.lamsfoundation.lams.dao.IBaseDAO; import org.lamsfoundation.lams.gradebook.service.IGradebookService; -import org.lamsfoundation.lams.learningdesign.Activity; -import org.lamsfoundation.lams.learningdesign.ActivityEvaluation; -import org.lamsfoundation.lams.learningdesign.BranchActivityEntry; -import org.lamsfoundation.lams.learningdesign.BranchingActivity; -import org.lamsfoundation.lams.learningdesign.Competence; -import org.lamsfoundation.lams.learningdesign.CompetenceMapping; -import org.lamsfoundation.lams.learningdesign.ComplexActivity; -import org.lamsfoundation.lams.learningdesign.FloatingActivity; -import org.lamsfoundation.lams.learningdesign.GateActivity; -import org.lamsfoundation.lams.learningdesign.Group; -import org.lamsfoundation.lams.learningdesign.Grouping; -import org.lamsfoundation.lams.learningdesign.GroupingActivity; -import org.lamsfoundation.lams.learningdesign.LearningDesign; -import org.lamsfoundation.lams.learningdesign.LearningDesignAccess; +import org.lamsfoundation.lams.learningdesign.*; import org.lamsfoundation.lams.learningdesign.LearningDesignAccess.LearningDesignAccessPrimaryKey; -import org.lamsfoundation.lams.learningdesign.License; -import org.lamsfoundation.lams.learningdesign.SequenceActivity; -import org.lamsfoundation.lams.learningdesign.ToolActivity; -import org.lamsfoundation.lams.learningdesign.Transition; -import org.lamsfoundation.lams.learningdesign.dao.IActivityDAO; -import org.lamsfoundation.lams.learningdesign.dao.IBranchActivityEntryDAO; -import org.lamsfoundation.lams.learningdesign.dao.ICompetenceDAO; -import org.lamsfoundation.lams.learningdesign.dao.ICompetenceMappingDAO; -import org.lamsfoundation.lams.learningdesign.dao.IGroupDAO; -import org.lamsfoundation.lams.learningdesign.dao.IGroupingDAO; -import org.lamsfoundation.lams.learningdesign.dao.ILearningDesignDAO; -import org.lamsfoundation.lams.learningdesign.dao.ILearningLibraryDAO; -import org.lamsfoundation.lams.learningdesign.dao.ILicenseDAO; -import org.lamsfoundation.lams.learningdesign.dao.ITransitionDAO; +import org.lamsfoundation.lams.learningdesign.dao.*; import org.lamsfoundation.lams.learningdesign.dto.LicenseDTO; import org.lamsfoundation.lams.learningdesign.dto.ValidationErrorDTO; import org.lamsfoundation.lams.learningdesign.exception.LearningDesignException; @@ -107,22 +66,19 @@ import org.lamsfoundation.lams.usermanagement.exception.UserAccessDeniedException; import org.lamsfoundation.lams.usermanagement.exception.UserException; import org.lamsfoundation.lams.usermanagement.exception.WorkspaceFolderException; -import org.lamsfoundation.lams.util.AuthoringJsonTags; -import org.lamsfoundation.lams.util.Configuration; -import org.lamsfoundation.lams.util.ConfigurationKeys; -import org.lamsfoundation.lams.util.JsonUtil; -import org.lamsfoundation.lams.util.MessageService; -import org.lamsfoundation.lams.util.WebUtil; +import org.lamsfoundation.lams.util.*; import org.lamsfoundation.lams.web.session.SessionManager; import org.lamsfoundation.lams.web.util.AttributeNames; import org.lamsfoundation.lams.workspace.dto.FolderContentDTO; import org.lamsfoundation.lams.workspace.service.IWorkspaceManagementService; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; +import javax.servlet.http.HttpSession; +import java.io.File; +import java.io.IOException; +import java.text.ParseException; +import java.util.*; /** * @author Manpreet Minhas @@ -196,7 +152,7 @@ /** * @param groupDAO - * The groupDAO to set. + * The groupDAO to set. */ public void setGroupDAO(IGroupDAO groupDAO) { this.groupDAO = groupDAO; @@ -220,63 +176,63 @@ /** * @param transitionDAO - * The transitionDAO to set + * The transitionDAO to set */ public void setTransitionDAO(ITransitionDAO transitionDAO) { this.transitionDAO = transitionDAO; } /** * @param learningDesignDAO - * The learningDesignDAO to set. + * The learningDesignDAO to set. */ public void setLearningDesignDAO(ILearningDesignDAO learningDesignDAO) { this.learningDesignDAO = learningDesignDAO; } /** * @param learningLibraryDAO - * The learningLibraryDAO to set. + * The learningLibraryDAO to set. */ public void setLearningLibraryDAO(ILearningLibraryDAO learningLibraryDAO) { this.learningLibraryDAO = learningLibraryDAO; } /** * @param baseDAO - * The baseDAO to set. + * The baseDAO to set. */ public void setBaseDAO(IBaseDAO baseDAO) { this.baseDAO = baseDAO; } /** * @param activityDAO - * The activityDAO to set. + * The activityDAO to set. */ public void setActivityDAO(IActivityDAO activityDAO) { this.activityDAO = activityDAO; } /** * @param toolDAO - * The toolDAO to set + * The toolDAO to set */ public void setToolDAO(IToolDAO toolDAO) { this.toolDAO = toolDAO; } /** * @param toolDAO - * The toolDAO to set + * The toolDAO to set */ public void setSystemToolDAO(ISystemToolDAO systemToolDAO) { this.systemToolDAO = systemToolDAO; } /** * @param licenseDAO - * The licenseDAO to set + * The licenseDAO to set */ public void setLicenseDAO(ILicenseDAO licenseDAO) { this.licenseDAO = licenseDAO; @@ -288,7 +244,7 @@ /** * @param learningDesignService - * The Learning Design Validator Service + * The Learning Design Validator Service */ public void setLearningDesignService(ILearningDesignService learningDesignService) { this.learningDesignService = learningDesignService; @@ -320,7 +276,7 @@ /** * @param contentIDGenerator - * The contentIDGenerator to set. + * The contentIDGenerator to set. */ public void setContentIDGenerator(ToolContentIDGenerator contentIDGenerator) { this.contentIDGenerator = contentIDGenerator; @@ -339,7 +295,7 @@ * Returns a populated LearningDesign object corresponding to the given learningDesignID * * @param learningDesignID - * The learning_design_id of the design which has to be fetched + * The learning_design_id of the design which has to be fetched * @return LearningDesign The populated LearningDesign object corresponding to the given learningDesignID */ private LearningDesign getLearningDesign(Long learningDesignID) { @@ -359,7 +315,7 @@ /** * @see org.lamsfoundation.lams.authoring.service.IAuthoringFullService#getToolOutputDefinitions(java.lang.Long, - * int) + * int) */ @Override public List getToolOutputDefinitions(Long toolContentID, int definitionType) { @@ -377,7 +333,7 @@ /** * @see org.lamsfoundation.lams.authoring.service.IAuthoringFullService#setupEditOnFlyLock(LearningDesign, - * java.lang.Integer) + * java.lang.Integer) */ @SuppressWarnings("unchecked") @Override @@ -394,9 +350,9 @@ throw new LearningDesignException("Learning Design was not found, ID: " + learningDesignID); } - boolean learningDesignAvailable = (design.getEditOverrideUser() == null) - || (design.getEditOverrideLock() == null) || design.getEditOverrideUser().getUserId().equals(userID) - || !design.getEditOverrideLock(); + boolean learningDesignAvailable = + (design.getEditOverrideUser() == null) || (design.getEditOverrideLock() == null) + || design.getEditOverrideUser().getUserId().equals(userID) || !design.getEditOverrideLock(); if (learningDesignAvailable) { if (design.getLessons().isEmpty()) { @@ -430,7 +386,7 @@ /** * @see org.lamsfoundation.lams.authoring.service.IAuthoringFullService#setupEditOnFlyGate(java.lang.Long, - * java.lang.Integer) + * java.lang.Integer) */ @Override @@ -466,8 +422,8 @@ throw new IOException("User not found, ID: " + userID); } - LearningDesign design = learningDesignID == null ? null - : learningDesignDAO.getLearningDesignById(learningDesignID); + LearningDesign design = + learningDesignID == null ? null : learningDesignDAO.getLearningDesignById(learningDesignID); if (design == null) { throw new IOException("Learning Design not found, ID: " + learningDesignID); @@ -485,8 +441,8 @@ processor.parseLearningDesign(); Activity lastReadOnlyActivity = processor.lastReadOnlyActivity; - Long firstAddedActivityId = processor.firstAddedActivity == null ? null - : processor.firstAddedActivity.getActivityId(); + Long firstAddedActivityId = + processor.firstAddedActivity == null ? null : processor.firstAddedActivity.getActivityId(); // open and release waiting list on system gate if ((lastReadOnlyActivity != null) && lastReadOnlyActivity.isGateActivity() @@ -498,8 +454,9 @@ for (Lesson lesson : design.getLessons()) { lesson.setLockedForEdit(false); - String message = messageService.getMessage("audit.live.edit.end", new Object[] { design.getTitle(), - design.getLearningDesignId(), lesson.getLessonId(), user.getLogin(), user.getUserId() }); + String message = messageService.getMessage("audit.live.edit.end", + new Object[] { design.getTitle(), design.getLearningDesignId(), lesson.getLessonId(), + user.getLogin(), user.getUserId() }); logEventService.logEvent(LogEvent.TYPE_LIVE_EDIT, user.getUserId(), null, lesson.getLessonId(), null, message); @@ -622,7 +579,8 @@ // set x / y position for Gate Integer x1 = toActivity.getXcoord() == null ? 0 : toActivity.getXcoord(); - Integer x2 = toActivity.getTransitionFrom() == null ? null + Integer x2 = toActivity.getTransitionFrom() == null + ? null : toActivity.getTransitionFrom().getToActivity().getXcoord(); if ((x1 != null) && (x2 != null)) { @@ -646,7 +604,8 @@ activity.setTransitionFrom(newTransition); gate.setTransitionTo(newTransition); - Integer x1 = activity.getTransitionTo() == null ? 0 + Integer x1 = activity.getTransitionTo() == null + ? 0 : activity.getTransitionTo().getFromActivity().getXcoord(); /* set x/y position for Gate */ Integer x2 = activity.getXcoord() == null ? null : activity.getXcoord(); @@ -692,8 +651,8 @@ if (toActivity.isBranchingActivity()) { // get real instance instead of lazy loaded proxy - BranchingActivity branchingActivity = (BranchingActivity) activityDAO - .getActivityByActivityId(toActivity.getActivityId()); + BranchingActivity branchingActivity = (BranchingActivity) activityDAO.getActivityByActivityId( + toActivity.getActivityId()); x2 = branchingActivity.getEndXcoord(); y2 = branchingActivity.getEndYcoord(); } else { @@ -748,8 +707,8 @@ // a content record, then shortcomings in the createToolSession // code may throw exceptions. if (activity.isToolActivity()) { - ToolActivity toolActivity = (ToolActivity) activityDAO - .getActivityByActivityId(activity.getActivityId()); + ToolActivity toolActivity = (ToolActivity) activityDAO.getActivityByActivityId( + activity.getActivityId()); Long newContentId = lamsCoreToolService.notifyToolToCopyContent(toolActivity, null); toolActivity.setToolContentId(newContentId); @@ -804,8 +763,8 @@ /** * @see org.lamsfoundation.lams.authoring.service.IAuthoringFullService#copyLearningDesign(org.lamsfoundation.lams.learningdesign.LearningDesign, - * java.lang.Integer, org.lamsfoundation.lams.usermanagement.User, - * org.lamsfoundation.lams.usermanagement.WorkspaceFolder, java.lang.Boolean, java.lang.String) + * java.lang.Integer, org.lamsfoundation.lams.usermanagement.User, + * org.lamsfoundation.lams.usermanagement.WorkspaceFolder, java.lang.Boolean, java.lang.String) */ @Override public LearningDesign copyLearningDesign(LearningDesign originalLearningDesign, Integer copyType, User user, @@ -843,7 +802,7 @@ LearningDesignService.getLearningDesignSVGPath(originalLearningDesign.getLearningDesignId())); if (sourceSVG.canRead()) { FileUtils.copyFile(sourceSVG, new File( - LearningDesignService.getLearningDesignSVGPath(newLearningDesign.getLearningDesignId())), + LearningDesignService.getLearningDesignSVGPath(newLearningDesign.getLearningDesignId())), false); } } catch (IOException e) { @@ -855,12 +814,12 @@ /** * @see org.lamsfoundation.lams.authoring.service.IAuthoringFullService#copyLearningDesignToolContent(org.lamsfoundation.lams.learningdesign.LearningDesign, - * org.lamsfoundation.lams.learningdesign.LearningDesign, java.lang.Integer) + * org.lamsfoundation.lams.learningdesign.LearningDesign, java.lang.Integer) */ private LearningDesign copyLearningDesignToolContent(LearningDesign design, LearningDesign originalLearningDesign, Integer copyType, String customCSV) throws LearningDesignException { - for (Iterator i = design.getActivities().iterator(); i.hasNext();) { + for (Iterator i = design.getActivities().iterator(); i.hasNext(); ) { Activity currentActivity = (Activity) i.next(); if (currentActivity.isToolActivity()) { copyActivityToolContent(currentActivity, design.getCopyTypeID(), @@ -896,9 +855,10 @@ log.error(error, e); throw new LearningDesignException(error, e); } catch (ToolException e) { - String error = "Unable to copy a design / initialise the lesson. Tool encountered an error copying the data is missing for activity " - + activity.getActivityUIID() + " in learning design " + originalLearningDesignId - + " default content may be missing for the tool. Error was " + e.getMessage(); + String error = + "Unable to copy a design / initialise the lesson. Tool encountered an error copying the data is missing for activity " + + activity.getActivityUIID() + " in learning design " + originalLearningDesignId + + " default content may be missing for the tool. Error was " + e.getMessage(); log.error(error, e); throw new LearningDesignException(error, e); } @@ -914,11 +874,11 @@ * doesn't matter). * * @param originalLearningDesign - * The LearningDesign to be copied + * The LearningDesign to be copied * @param newLearningDesign - * The copy of the originalLearningDesign + * The copy of the originalLearningDesign * @return Map of all the new activities, where the key is the UIID value. This is used as an input to - * updateDesignTransitions + * updateDesignTransitions */ private HashMap updateDesignActivities(LearningDesign originalLearningDesign, LearningDesign newLearningDesign, int uiidOffset, String customCSV) { @@ -962,8 +922,8 @@ ComplexActivity newComplex = (ComplexActivity) activity; Activity oldDefaultActivity = newComplex.getDefaultActivity(); if (oldDefaultActivity != null) { - Activity newDefaultActivity = newActivities - .get(LearningDesign.addOffset(oldDefaultActivity.getActivityUIID(), uiidOffset)); + Activity newDefaultActivity = newActivities.get( + LearningDesign.addOffset(oldDefaultActivity.getActivityUIID(), uiidOffset)); newComplex.setDefaultActivity(newDefaultActivity); } } @@ -973,8 +933,8 @@ // Need to check if the sets are not null as these are new // objects and Hibernate may not have backed them with // collections yet. - if ((newSequenceActivity.getBranchEntries() != null) - && (newSequenceActivity.getBranchEntries().size() > 0)) { + if ((newSequenceActivity.getBranchEntries() != null) && (newSequenceActivity.getBranchEntries().size() + > 0)) { Activity parentActivity = newSequenceActivity.getParentActivity(); if (parentActivity.isChosenBranchingActivity()) { @@ -991,16 +951,16 @@ // copy BranchActivityEntry entry = (BranchActivityEntry) beIter.next(); BranchingActivity oldBranchingActivity = (BranchingActivity) entry.getBranchingActivity(); - entry.setBranchingActivity(newActivities - .get(LearningDesign.addOffset(oldBranchingActivity.getActivityUIID(), uiidOffset))); + entry.setBranchingActivity(newActivities.get( + LearningDesign.addOffset(oldBranchingActivity.getActivityUIID(), uiidOffset))); Group oldGroup = entry.getGroup(); if (oldGroup != null) { Grouping oldGrouping = oldGroup.getGrouping(); - Grouping newGrouping = newGroupings - .get(LearningDesign.addOffset(oldGrouping.getGroupingUIID(), uiidOffset)); + Grouping newGrouping = newGroupings.get( + LearningDesign.addOffset(oldGrouping.getGroupingUIID(), uiidOffset)); if (newGrouping != null) { - entry.setGroup(newGrouping - .getGroup(LearningDesign.addOffset(oldGroup.getGroupUIID(), uiidOffset))); + entry.setGroup(newGrouping.getGroup( + LearningDesign.addOffset(oldGroup.getGroupUIID(), uiidOffset))); } } } @@ -1014,8 +974,8 @@ Iterator inputIter = activity.getInputActivities().iterator(); while (inputIter.hasNext()) { Activity elem = (Activity) inputIter.next(); - newInputActivities - .add(newActivities.get(LearningDesign.addOffset(elem.getActivityUIID(), uiidOffset))); + newInputActivities.add( + newActivities.get(LearningDesign.addOffset(elem.getActivityUIID(), uiidOffset))); } activity.getInputActivities().clear(); activity.getInputActivities().addAll(newInputActivities); @@ -1060,16 +1020,15 @@ * with their new groupings at the end. Also copies the tool content. * * @param activity - * Activity to process. May not be null. + * Activity to process. May not be null. * @param newLearningDesign - * The new learning design. May not be null. + * The new learning design. May not be null. * @param newActivities - * Temporary set of new activities - as activities are processed they are added to the set. May not be - * null. + * Temporary set of new activities - as activities are processed they are added to the set. May not be null. * @param newGroupings - * Temporary set of new groupings. Key is the grouping UUID. May not be null. + * Temporary set of new groupings. Key is the grouping UUID. May not be null. * @param parentActivity - * This activity's parent activity (if one exists). May be null. + * This activity's parent activity (if one exists). May be null. */ private void processActivity(Activity activity, LearningDesign newLearningDesign, Map newActivities, Map newGroupings, Activity parentActivity, @@ -1129,9 +1088,9 @@ * Updates the Transition information in the newLearningDesign based on the originalLearningDesign * * @param originalLearningDesign - * The LearningDesign to be copied + * The LearningDesign to be copied * @param newLearningDesign - * The copy of the originalLearningDesign + * The copy of the originalLearningDesign */ private void updateDesignTransitions(LearningDesign originalLearningDesign, LearningDesign newLearningDesign, HashMap newActivities, int uiidOffset) { @@ -1185,9 +1144,9 @@ * Updates the competence information in the newLearningDesign based on the originalLearningDesign * * @param originalLearningDesign - * The LearningDesign to be copied + * The LearningDesign to be copied * @param newLearningDesign - * The copy of the originalLearningDesign + * The copy of the originalLearningDesign */ private void updateDesignCompetences(LearningDesign originalLearningDesign, LearningDesign newLearningDesign, boolean insert) { @@ -1236,9 +1195,9 @@ * Updates the competence information in the newLearningDesign based on the originalLearningDesign * * @param originalLearningDesign - * The LearningDesign to be copied + * The LearningDesign to be copied * @param newLearningDesign - * The copy of the originalLearningDesign + * The copy of the originalLearningDesign */ private void updateCompetenceMappings(Set newCompetences, HashMap newActivities) { for (Integer activityKey : newActivities.keySet()) { @@ -1271,9 +1230,9 @@ * Determines the type of activity and returns a deep-copy of the same * * @param activity - * The object to be deep-copied + * The object to be deep-copied * @param newGroupings - * Temporary set of new groupings. Key is the grouping UUID. May not be null. + * Temporary set of new groupings. Key is the grouping UUID. May not be null. * @return Activity The new deep-copied Activity object */ private Activity getActivityCopy(final Activity activity, Map newGroupings, int uiidOffset) { @@ -1298,7 +1257,7 @@ * Returns a set of child activities for the given parent activity * * @param parentActivity - * The parent activity + * The parent activity * @return HashSet Set of the activities that belong to the parentActivity */ private HashSet getChildActivities(Activity parentActivity) { @@ -1331,15 +1290,16 @@ } Long learningDesignId = JsonUtil.optLong(ldJSON, AttributeNames.PARAM_LEARNINGDESIGN_ID); - LearningDesign existingLearningDesign = learningDesignId == null ? null - : learningDesignDAO.getLearningDesignById(learningDesignId); - if (!authorised && (existingLearningDesign != null) - && Boolean.TRUE.equals(existingLearningDesign.getEditOverrideLock())) { + LearningDesign existingLearningDesign = + learningDesignId == null ? null : learningDesignDAO.getLearningDesignById(learningDesignId); + if (!authorised && (existingLearningDesign != null) && Boolean.TRUE.equals( + existingLearningDesign.getEditOverrideLock())) { authorised = userID.equals(existingLearningDesign.getEditOverrideUser().getUserId()); } if (!authorised) { - throw new UserException("User with ID " + userID - + " is not authorized to store a design in this workspace folder " + workspaceFolderID); + throw new UserException( + "User with ID " + userID + " is not authorized to store a design in this workspace folder " + + workspaceFolderID); } if (existingLearningDesign == null) { // check the user has given it a unique name in this folder, and make it unique if needed @@ -1352,8 +1312,8 @@ ldJSON.put(AuthoringJsonTags.TITLE, title); } - IObjectExtractor extractor = (IObjectExtractor) beanFactory - .getBean(IObjectExtractor.OBJECT_EXTRACTOR_SPRING_BEANNAME); + IObjectExtractor extractor = (IObjectExtractor) beanFactory.getBean( + IObjectExtractor.OBJECT_EXTRACTOR_SPRING_BEANNAME); LearningDesign design = extractor.extractSaveLearningDesign(ldJSON, existingLearningDesign, workspaceFolder, user); @@ -1387,8 +1347,8 @@ @Override public Vector validateLearningDesign(Long learningDesignId) { LearningDesign learningDesign = learningDesignDAO.getLearningDesignById(learningDesignId); - Vector listOfValidationErrorDTOs = learningDesignService - .validateLearningDesign(learningDesign); + Vector listOfValidationErrorDTOs = learningDesignService.validateLearningDesign( + learningDesign); Boolean valid = listOfValidationErrorDTOs.size() > 0 ? Boolean.FALSE : Boolean.TRUE; learningDesign.setValidDesign(valid); learningDesignDAO.insertOrUpdate(learningDesign); @@ -1607,36 +1567,30 @@ /** * Helper method to create a Assessment tool content. Assessment is one of the unusuals tool in that it caches - * user's login names and - * first/last names Mandatory fields in toolContentJSON: title, instructions, resources, user fields firstName, - * lastName and loginName. + * user's login names and first/last names Mandatory fields in toolContentJSON: title, instructions, resources, user + * fields firstName, lastName and loginName. * * Required fields in toolContentJSON: "title", "instructions", "questions", "firstName", "lastName", "lastName", * "questions" and "references". * - * The questions entry should be ArrayNode containing JSON objects, which in turn must contain - * "questionTitle", "questionText", "displayOrder" (Integer), "type" (Integer). If the type is Multiple Choice, - * Numerical or Matching Pairs - * then a ArrayNode "answers" is required. + * The questions entry should be ArrayNode containing JSON objects, which in turn must contain "questionTitle", + * "questionText", "displayOrder" (Integer), "type" (Integer). If the type is Multiple Choice, Numerical or Matching + * Pairs then a ArrayNode "answers" is required. * - * The answers entry should be ArrayNode - * containing JSON objects, which in turn must contain "answerText" or "answerFloat", "displayOrder" (Integer), - * "grade" (Integer). + * The answers entry should be ArrayNode containing JSON objects, which in turn must contain "answerText" or + * "answerFloat", "displayOrder" (Integer), "grade" (Integer). * * For the templates, all the questions that are created will be set up as references, therefore the questions in - * the assessment == the bank of questions. - * So references entry will be a ArrayNode containing JSON objects, which in turn must contain "displayOrder" - * (Integer), - * "questionDisplayOrder" (Integer - to match to the question). If default grade or random questions are needed then - * this method needs - * to be expanded. + * the assessment == the bank of questions. So references entry will be a ArrayNode containing JSON objects, which + * in turn must contain "displayOrder" (Integer), "questionDisplayOrder" (Integer - to match to the question). If + * default grade or random questions are needed then this method needs to be expanded. */ @Override public Long createTblAssessmentToolContent(UserDTO user, String title, String instructions, String reflectionInstructions, boolean selectLeaderToolOutput, boolean enableNumbering, boolean shuffleQuestions, boolean enableConfidenceLevels, boolean allowDiscloseAnswers, - boolean allowAnswerJustification, boolean enableDiscussionSentiment, ArrayNode questions) - throws IOException { + boolean allowAnswerJustification, boolean enableDiscussionSentiment, Long qbCollectionUid, + ArrayNode questions) throws IOException { ObjectNode toolContentJSON = AuthoringService.createStandardToolContent(title, instructions, reflectionInstructions, null, null, user); @@ -1664,6 +1618,10 @@ .put("questionDisplayOrder", questionDisplayOrder).put("maxMark", defaultGrade)); } toolContentJSON.set("references", references); + + if (qbCollectionUid != null) { + toolContentJSON.put(RestTags.COLLECTION_UID, qbCollectionUid); + } } return createToolContent(user, "laasse10", toolContentJSON); Index: lams_central/src/java/org/lamsfoundation/lams/authoring/service/IAuthoringFullService.java =================================================================== diff -u -r8a6ef31e47f040438cce78848c3ccb238a9620b7 -rf4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a --- lams_central/src/java/org/lamsfoundation/lams/authoring/service/IAuthoringFullService.java (.../IAuthoringFullService.java) (revision 8a6ef31e47f040438cce78848c3ccb238a9620b7) +++ lams_central/src/java/org/lamsfoundation/lams/authoring/service/IAuthoringFullService.java (.../IAuthoringFullService.java) (revision f4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a) @@ -23,11 +23,8 @@ package org.lamsfoundation.lams.authoring.service; -import java.io.IOException; -import java.text.ParseException; -import java.util.List; -import java.util.Vector; - +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.lamsfoundation.lams.authoring.IAuthoringService; import org.lamsfoundation.lams.authoring.ObjectExtractorException; import org.lamsfoundation.lams.learningdesign.LearningDesign; @@ -40,8 +37,10 @@ import org.lamsfoundation.lams.usermanagement.exception.UserException; import org.lamsfoundation.lams.usermanagement.exception.WorkspaceFolderException; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.IOException; +import java.text.ParseException; +import java.util.List; +import java.util.Vector; /** * @author Manpreet Minhas @@ -149,5 +148,5 @@ Long createTblAssessmentToolContent(UserDTO user, String title, String instructions, String reflectionInstructions, boolean selectLeaderToolOutput, boolean enableNumbering, boolean shuffleQuestions, boolean enableConfidenceLevels, boolean allowDiscloseAnswers, boolean allowAnswerJustification, - boolean enableDiscussionSentiment, ArrayNode questions) throws IOException; + boolean enableDiscussionSentiment, Long qbCollectionUid, ArrayNode questions) throws IOException; } \ No newline at end of file Index: lams_central/src/java/org/lamsfoundation/lams/authoring/template/web/LdTemplateController.java =================================================================== diff -u -re2057581a5adc97cd2967e52b53737b3a863699f -rf4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a --- lams_central/src/java/org/lamsfoundation/lams/authoring/template/web/LdTemplateController.java (.../LdTemplateController.java) (revision e2057581a5adc97cd2967e52b53737b3a863699f) +++ lams_central/src/java/org/lamsfoundation/lams/authoring/template/web/LdTemplateController.java (.../LdTemplateController.java) (revision f4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a) @@ -22,24 +22,9 @@ */ package org.lamsfoundation.lams.authoring.template.web; -import java.io.File; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.text.DecimalFormat; -import java.text.MessageFormat; -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.ResourceBundle; -import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicInteger; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; - +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.log4j.Logger; import org.lamsfoundation.lams.authoring.service.AuthoringService; import org.lamsfoundation.lams.authoring.service.IAuthoringFullService; @@ -77,23 +62,28 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.WebApplicationContext; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.text.DecimalFormat; +import java.text.MessageFormat; +import java.text.NumberFormat; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; /** * Base class for actions processing Learning Design templates. * * A little history: This code was written when we were still using Struts but we were phasing it out. So it was written - * with the - * minimal of Struts code, which was then swapped to being the minimal Spring MVC. Now we are using Spring MVC it would - * be nice to convert to using more Spring MVC features rather than adding parts piecemeal as the template pages - * become more complicated. Rather than relying on the web page to keep all the data and doing ajax updates, it would be - * nice to have a session form or the like backing the page. It would make it easier to have the templates load an - * existing - * design from the database for modification and make the processing when the template is saved so it doesn't do one - * huge - * parse in one go. + * with the minimal of Struts code, which was then swapped to being the minimal Spring MVC. Now we are using Spring MVC + * it would be nice to convert to using more Spring MVC features rather than adding parts piecemeal as the template + * pages become more complicated. Rather than relying on the web page to keep all the data and doing ajax updates, it + * would be nice to have a session form or the like backing the page. It would make it easier to have the templates load + * an existing design from the database for modification and make the processing when the template is saved so it + * doesn't do one huge parse in one go. * * Would also be nice to stop hardcoding the icons! * @@ -206,12 +196,12 @@ /** * If you have a CKEditor in your template interface, you should set up an "init" forward for your servlet in * response to this call (e.g. pbl.do?method=init) and it should forward to your JSP form. Calling this method sets - * up the contentFolderID needed by the CKEditor to support image upload. The fields that match these values - * are in init.jsp and should be included in the JSP form. The JSP should then call the unspecified - * method (e.g. pbl.do) to process the form. + * up the contentFolderID needed by the CKEditor to support image upload. The fields that match these values are in + * init.jsp and should be included in the JSP form. The JSP should then call the unspecified method (e.g. pbl.do) to + * process the form. * - * If you wish to set up other values in the initialisation that are form specific, override this method - * in your servlet, remembering to call this method. + * If you wish to set up other values in the initialisation that are form specific, override this method in your + * servlet, remembering to call this method. */ @RequestMapping("/init") public String init(HttpServletRequest request) throws Exception { @@ -230,19 +220,18 @@ throws Exception; /** - * Creates transitions between activities in the order they were created. - * Simple version used if no sequence activities are used. + * Creates transitions between activities in the order they were created. Simple version used if no sequence + * activities are used. */ protected ArrayNode createTransitions(AtomicInteger maxUIID, ArrayNode activities) { ArrayNode transitions = JsonNodeFactory.instance.arrayNode(); return createTransitions(transitions, maxUIID, activities); } /** - * Creates transitions between activities in the order they were created. - * Complex version used if sequence activities are used - then the top level activities go in one set and each - * sequence - * activity is its own set of activities. + * Creates transitions between activities in the order they were created. Complex version used if sequence + * activities are used - then the top level activities go in one set and each sequence activity is its own set of + * activities. */ protected ArrayNode createTransitions(AtomicInteger maxUIID, Set setsOfActivities) { ArrayNode transitions = JsonNodeFactory.instance.arrayNode(); @@ -255,8 +244,8 @@ } /** - * Processes the transitions between activities within a sequence. This could be the overall - * sequence or activities within a SequenceActivity. The transitions parameter must not be null. + * Processes the transitions between activities within a sequence. This could be the overall sequence or activities + * within a SequenceActivity. The transitions parameter must not be null. */ private ArrayNode createTransitions(ArrayNode transitions, AtomicInteger maxUIID, ArrayNode activities) { @@ -266,8 +255,8 @@ ObjectNode fromActivity = (ObjectNode) activities.get(activityIndex - 1); ObjectNode toActivity = (ObjectNode) activities.get(activityIndex); Integer parentType = JsonUtil.optInt(toActivity, PARENT_ACTIVITY_TYPE, (Integer) null); - while ((activityIndex < activities.size()) && parentType != null - && !parentType.equals(Activity.SEQUENCE_ACTIVITY_TYPE)) { + while ((activityIndex < activities.size()) && parentType != null && !parentType.equals( + Activity.SEQUENCE_ACTIVITY_TYPE)) { activityIndex++; toActivity = (ObjectNode) activities.get(activityIndex); parentType = JsonUtil.optInt(toActivity, PARENT_ACTIVITY_TYPE, (Integer) null); @@ -296,10 +285,9 @@ protected static final int gateWidthOffset = 70; /** - * Calculate where to draw an activity. Aim for 4 activities per line. Returns Integer[x,y] - * Used when there is no hardcoding of location of activities. - * If some activities use hardcoding of layout, then see calcPositionNextRight(), calcPositionBelow() - * to calculate relative positions. + * Calculate where to draw an activity. Aim for 4 activities per line. Returns Integer[x,y] Used when there is no + * hardcoding of location of activities. If some activities use hardcoding of layout, then see + * calcPositionNextRight(), calcPositionBelow() to calculate relative positions. * * @return */ @@ -340,8 +328,8 @@ /** * Create a title for this learning design, within the right length for the database. The userEnteredString is - * capitalised and whitespace is removed. The call to saveLearningDesign will make it unique by appending a date - * if needed. + * capitalised and whitespace is removed. The call to saveLearningDesign will make it unique by appending a date if + * needed. * * @param sequenceTitle * @param workspaceFolderID @@ -360,9 +348,8 @@ * Setup Learning Design JSON Data * * @throws IOException + * @throws HttpException * @ - * @throws - * HttpException */ protected ObjectNode saveLearningDesign(String templateCode, String userEnteredTitleString, String userEnteredDescription, Integer workspaceFolderID, String contentFolderId, Integer maxUIID, @@ -540,8 +527,8 @@ } /** - * Create a support activity group from already created activities. The support activities run outside the - * sequenced activities + * Create a support activity group from already created activities. The support activities run outside the sequenced + * activities */ protected ObjectNode createSupportActivity(AtomicInteger uiid, int order, Integer[] layoutCoords) { Integer[] pos = layoutCoords; @@ -564,14 +551,12 @@ } /** - * Create a sequence (or branch) activity, which is a single path that a user follows. This activity will go - * into a branching activity. If branchName is null then the branch will be "Branch ". - * The sequence activity has a circular dependence - to define the SequenceActivity you need the UIID of the first - * activity in the sequence. But the first activity in the sequence needs the SequenceActivity's UIID as its parent - * activity UIID. - * So "reserve" a uuid (using incrementAndGet()) in the template for the SequenceActivity, create the child activity - * (setting - * the parent activity as the reserved uiid) and then create the SequenceActivity using the reserved uiid. + * Create a sequence (or branch) activity, which is a single path that a user follows. This activity will go into a + * branching activity. If branchName is null then the branch will be "Branch ". The sequence activity has a + * circular dependence - to define the SequenceActivity you need the UIID of the first activity in the sequence. But + * the first activity in the sequence needs the SequenceActivity's UIID as its parent activity UIID. So "reserve" a + * uuid (using incrementAndGet()) in the template for the SequenceActivity, create the child activity (setting the + * parent activity as the reserved uiid) and then create the SequenceActivity using the reserved uiid. */ protected ObjectNode createSequenceActivity(Integer reservedUiid, Integer parentUIID, Integer parentActivityType, Integer firstActivityUiid, int orderId, String branchName) { @@ -591,8 +576,8 @@ /** * Map a branch to a group. Creates JSON similar to: - * {"entryUIID":16,"groupUIID":11,"branchingActivityUIID":1,"sequenceActivityUIID":3} - * Note: it needs the UIID of the matching group, not of the grouping activity + * {"entryUIID":16,"groupUIID":11,"branchingActivityUIID":1,"sequenceActivityUIID":3} Note: it needs the UIID of the + * matching group, not of the grouping activity * * @ */ @@ -608,15 +593,14 @@ } /** - * Create the overall branching activity, which will have branch activities as its children. - * Branch selected based on a group. + * Create the overall branching activity, which will have branch activities as its children. Branch selected based + * on a group. * * The branching activity has a circular dependence - to define the BranchingActivity you need the UIID of the - * default branch (sequence). - * But the default sequence needs the BranchingActivity's UIID as its parent activity UIID. - * So "reserve" a uuid (using incrementAndGet()) in the template for the BranchingActivity, create the branches - * (setting - * the parent activity as the reserved uiid) and then create the BranchingActivity using the reserved uiid. + * default branch (sequence). But the default sequence needs the BranchingActivity's UIID as its parent activity + * UIID. So "reserve" a uuid (using incrementAndGet()) in the template for the BranchingActivity, create the + * branches (setting the parent activity as the reserved uiid) and then create the BranchingActivity using the + * reserved uiid. */ protected ObjectNode createGroupBranchingActivity(Integer reservedUiid, Integer defaultBranchUiid, Integer groupingUiid, int order, Integer[] layoutCoords, Integer[] startCoords, Integer[] endCoords, @@ -635,9 +619,9 @@ // } /** - * Create the overall branching activity, which will have branch activities as its children. - * Supports instructor's choice and grouped, but not based on learner output as conditions - * are not implemented in the REST authoring for the tools + * Create the overall branching activity, which will have branch activities as its children. Supports instructor's + * choice and grouped, but not based on learner output as conditions are not implemented in the REST authoring for + * the tools */ private ObjectNode createBranchingActivity(Integer reservedUiid, Integer defaultBranchUiid, Integer groupingUiid, int order, Integer[] layoutCoords, Integer[] startCoords, Integer[] endCoords, String activityTitle, @@ -692,7 +676,8 @@ activityJSON.put(AuthoringJsonTags.TOOL_ID, tool.getToolId()); activityJSON.put(AuthoringJsonTags.LEARNING_LIBRARY_ID, tool.getLearningLibraryId()); activityJSON.put(AuthoringJsonTags.TOOL_CONTENT_ID, toolContentID); - activityJSON.put(AuthoringJsonTags.GROUPING_SUPPORT_TYPE, 2); // based on values in lams_learning_activity field - this seem to be + activityJSON.put(AuthoringJsonTags.GROUPING_SUPPORT_TYPE, + 2); // based on values in lams_learning_activity field - this seem to be // 2 for all tools activityJSON.put(AuthoringJsonTags.LIBRARY_IMAGE, toolIcon); activityJSON.put(AuthoringJsonTags.XCOORD, pos[0]); @@ -730,8 +715,7 @@ /** * Helper method to create a chat tool content. Only title and instructions are needed but we support reflection, - * lock on - * finished and filterKeywords in case it is wanted. The keywords should be a comma deliminated string. + * lock on finished and filterKeywords in case it is wanted. The keywords should be a comma deliminated string. */ protected Long createChatToolContent(UserDTO user, String title, String instructions, boolean lockWhenFinished, String filterKeywords, String reflectionInstructions) throws IOException { @@ -766,10 +750,10 @@ * notifyTeachersOnForumPosting reflectInstructions, reflectOnActivity, submissionDeadline * * @param limitedMaxCharacters - * Should the maximum number of characters in a posting be limited + * Should the maximum number of characters in a posting be limited * @param maxCharacters - * if limitedMaxCharacters == true then if maxCharacters == null let forum use default otherwise use the - * value supplied. + * if limitedMaxCharacters == true then if maxCharacters == null let forum use default otherwise use the value + * supplied. */ protected Long createForumToolContent(UserDTO user, String title, String instructions, boolean lockWhenFinished, boolean allowRichTextEditor, boolean allowNewTopic, boolean allowRateMessages, boolean allowUpload, @@ -1003,8 +987,8 @@ // TODO files - need to save it somehow, validate the file size, etc if (type != LdTemplateController.RESOURCE_TYPE_URL) { - LdTemplateController.log - .warn("LD Templates not handling files yet - file, website & LO resources won't work. Filename " + LdTemplateController.log.warn( + "LD Templates not handling files yet - file, website & LO resources won't work. Filename " + file.getAbsoluteFile()); } return item; @@ -1061,7 +1045,7 @@ */ protected Long createScratchieToolContent(UserDTO user, String title, String instructions, boolean useSelectLeaderToolOuput, boolean enableDiscussionSentiment, Integer confidenceLevelsActivityUiid, - ArrayNode questions) throws IOException { + Long qbCollectionUid, ArrayNode questions) throws IOException { ObjectNode toolContentJSON = AuthoringService.createStandardToolContent(title, instructions, null, null, null, null); @@ -1077,6 +1061,10 @@ question.put("answerRequired", true); } + if (qbCollectionUid != null) { + toolContentJSON.put(RestTags.COLLECTION_UID, qbCollectionUid); + } + return authoringService.createToolContent(user, LdTemplateController.SCRATCHIE_TOOL_SIGNATURE, toolContentJSON); } @@ -1122,9 +1110,8 @@ } /** - * Helper method to create a submit tool content. Another tool that caches user's login names and - * first/last names Mandatory fields: title & instructions; userDTO which gives user's firstName, lastName & - * loginName; + * Helper method to create a submit tool content. Another tool that caches user's login names and first/last names + * Mandatory fields: title & instructions; userDTO which gives user's firstName, lastName & loginName; */ protected Long createSubmitToolContent(UserDTO user, String title, String instructions, boolean lockWhenFinished, Boolean limitUpload, Integer limitUploadNumber, String reflectionInstructions) throws IOException { @@ -1151,8 +1138,8 @@ } /** - * Helper method to create a survey tool content. Another tool that caches user's login names and - * first/last names! See the survey implementation for the full field list. + * Helper method to create a survey tool content. Another tool that caches user's login names and first/last names! + * See the survey implementation for the full field list. */ protected Long createSurveyToolContent(UserDTO user, String title, String instructions, Boolean lockWhenFinished, ArrayNode questions) throws IOException { @@ -1226,9 +1213,8 @@ } /** - * Helper method to create a Peer Review tool content. - * Required fields in toolContentJSON: "title", "instructions", "questions", "firstName", "lastName", "lastName", - * "questions" and "references". + * Helper method to create a Peer Review tool content. Required fields in toolContentJSON: "title", "instructions", + * "questions", "firstName", "lastName", "lastName", "questions" and "references". * * The criterias entry should be ArrayNode as defined in PeerReviewCriters object. */ @@ -1285,11 +1271,9 @@ } /** - * * /* ************************************** Service related methods ********************************************** */ /* ************************************** I18N related methods ************************************************* */ - protected final Tool getTool(String toolSignature) { return toolDAO.getToolBySignature(toolSignature); } @@ -1310,8 +1294,8 @@ /********************************* Page Interaction Support *************************************/ /** - * Specialised call to create a new question for the Assessment fields. Returns a fragment of HTML - * which sets up a new CKEditor. Defaults to essay. If questionType = "mcq" then it will do a multiple choice + * Specialised call to create a new question for the Assessment fields. Returns a fragment of HTML which sets up a + * new CKEditor. Defaults to essay. If questionType = "mcq" then it will do a multiple choice */ @RequestMapping("/createAssessment") public String createAssessment(HttpServletRequest request) { @@ -1513,9 +1497,9 @@ } /** - * Specialised call to create a new question & options for the surveys tab. Returns a fragment of HTML - * which sets up a new CKEditor. Works with both mcquestion.jsp & surveyquestion.jsp. The template's - * struts action determines which jsp is used (see TBL and Inquiry uses). + * Specialised call to create a new question & options for the surveys tab. Returns a fragment of HTML which sets up + * a new CKEditor. Works with both mcquestion.jsp & surveyquestion.jsp. The template's struts action determines + * which jsp is used (see TBL and Inquiry uses). */ @RequestMapping("/createQuestion") public String createQuestion(HttpServletRequest request) { @@ -1579,9 +1563,8 @@ /** * Specialised call to create a new option for a multiple choice question (mcoption.jsp), Survey question - * (surveyoption.jsp) or assessment multiple choice (assessmcq.jsp). - * Returns a fragment of HTML which sets up the editing field. The template's - * struts action determines which jsp is used (see TBL and Inquiry uses). + * (surveyoption.jsp) or assessment multiple choice (assessmcq.jsp). Returns a fragment of HTML which sets up the + * editing field. The template's struts action determines which jsp is used (see TBL and Inquiry uses). */ @RequestMapping("/createOption") public String createOption(HttpServletRequest request) { @@ -1614,7 +1597,8 @@ request.setAttribute("options", options); request.setAttribute("optionCount", options.size()); request.setAttribute("containingDivName", containingDivName); - return (useAssessmentVersion ? "authoring/template/tool/assessredooption" + return (useAssessmentVersion + ? "authoring/template/tool/assessredooption" : "authoring/template/tool/mcredooption"); } @@ -1660,7 +1644,8 @@ request.setAttribute("options", options); request.setAttribute("optionCount", options.size()); request.setAttribute("containingDivName", WebUtil.readStrParam(request, "containingDivName", true)); - return (useAssessmentVersion ? "authoring/template/tool/assessredooption" + return (useAssessmentVersion + ? "authoring/template/tool/assessredooption" : "authoring/template/tool/mcredooption"); } @@ -1710,8 +1695,8 @@ // } /** - * Specialised call to create a new rating criteria for the Peer Review fields. Returns a fragment of HTML - * which sets up the new fields. + * Specialised call to create a new rating criteria for the Peer Review fields. Returns a fragment of HTML which + * sets up the new fields. */ @RequestMapping("/createRatingCriteria") public String createRatingCriteria(HttpServletRequest request) { Index: lams_central/src/java/org/lamsfoundation/lams/authoring/template/web/TBLTemplateController.java =================================================================== diff -u -r8a6ef31e47f040438cce78848c3ccb238a9620b7 -rf4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a --- lams_central/src/java/org/lamsfoundation/lams/authoring/template/web/TBLTemplateController.java (.../TBLTemplateController.java) (revision 8a6ef31e47f040438cce78848c3ccb238a9620b7) +++ lams_central/src/java/org/lamsfoundation/lams/authoring/template/web/TBLTemplateController.java (.../TBLTemplateController.java) (revision f4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a) @@ -22,33 +22,13 @@ */ package org.lamsfoundation.lams.authoring.template.web; -import java.io.IOException; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; - +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; -import org.lamsfoundation.lams.authoring.template.AssessMCAnswer; -import org.lamsfoundation.lams.authoring.template.Assessment; -import org.lamsfoundation.lams.authoring.template.PeerReviewCriteria; -import org.lamsfoundation.lams.authoring.template.TemplateData; -import org.lamsfoundation.lams.authoring.template.TextUtil; +import org.lamsfoundation.lams.authoring.template.*; +import org.lamsfoundation.lams.qb.model.QbCollection; import org.lamsfoundation.lams.qb.model.QbQuestion; import org.lamsfoundation.lams.rest.RestTags; import org.lamsfoundation.lams.usermanagement.dto.UserDTO; @@ -61,15 +41,23 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** - * A Team Based Learning template. There are two versions - the full blown one that is accessed from authoring - * and one that allows just sections of a TBL sequence to be created for the LAMS TBL system. Which bits are created - * are controlled by a series of checkboxes (set to true and hidden in the full authoring version). The LAMS TBL - * version also has the option for timed gates, but the standard version uses permission gates. + * A Team Based Learning template. There are two versions - the full blown one that is accessed from authoring and one + * that allows just sections of a TBL sequence to be created for the LAMS TBL system. Which bits are created are + * controlled by a series of checkboxes (set to true and hidden in the full authoring version). The LAMS TBL version + * also has the option for timed gates, but the standard version uses permission gates. */ @Controller @RequestMapping("authoring/template/tbl") @@ -92,6 +80,13 @@ if (sessionMapID != null) { request.setAttribute("sessionMapID", sessionMapID); } + HttpSession session = request.getSession(); + UserDTO userDTO = (UserDTO) session.getAttribute(AttributeNames.USER); + if (userDTO != null) { + List qbCollections = qbService.getUserCollections(userDTO.getUserID()); + request.setAttribute("qbCollections", qbCollections); + } + if (isConfigurableVersion) { return super.init(request, "authoring/template/tbl/tbloptional"); } else { @@ -118,7 +113,8 @@ ArrayNode activities = JsonNodeFactory.instance.arrayNode(); ArrayNode groupings = JsonNodeFactory.instance.arrayNode(); - Integer[] firstActivityInRowPosition = new Integer[] { 40, 40 }; // the very first activity, all other locations can be calculated from here if needed! + Integer[] firstActivityInRowPosition = new Integer[] { 40, + 40 }; // the very first activity, all other locations can be calculated from here if needed! String activityTitle = null; Integer[] currentActivityPosition = null; Integer groupingUIID = null; @@ -144,6 +140,8 @@ groupings.add(groupingJSONs[1]); groupingUIID = groupingJSONs[1].get("groupingUIID").asInt(); + Long qbCollectionUid = data.qbCollectionUid; + if (data.useIRATRA) { // Stop! currentActivityPosition = calcPositionNextRight(currentActivityPosition); @@ -152,8 +150,9 @@ activities.add(createScheduledGateActivity(maxUIID, order++, calcGateOffset(currentActivityPosition), activityTitle, null, data.iraStartOffset, true)); } else { - activities.add(createGateActivity(maxUIID, order++, calcGateOffset(currentActivityPosition), - activityTitle, activityTitle, true)); + activities.add( + createGateActivity(maxUIID, order++, calcGateOffset(currentActivityPosition), activityTitle, + activityTitle, true)); } // iRA Test - Assessment @@ -163,7 +162,7 @@ Long iRAToolContentId = authoringService.createTblAssessmentToolContent(userDTO, activityTitle, data.getText("boilerplate.ira.instructions"), null, false, true, data.shuffleIRAT, - data.confidenceLevelEnable, false, false, false, testQuestionsArray); + data.confidenceLevelEnable, false, false, false, qbCollectionUid, testQuestionsArray); ObjectNode iraActivityJSON = createAssessmentActivity(maxUIID, order++, currentActivityPosition, iRAToolContentId, data.contentFolderID, groupingUIID, null, null, activityTitle); activities.add(iraActivityJSON); @@ -184,19 +183,19 @@ activities.add(createScheduledGateActivity(maxUIID, order++, calcGateOffset(currentActivityPosition), activityTitle, null, data.traStartOffset, true)); } else { - activities.add(createGateActivity(maxUIID, order++, calcGateOffset(currentActivityPosition), - activityTitle, activityTitle, true)); + activities.add( + createGateActivity(maxUIID, order++, calcGateOffset(currentActivityPosition), activityTitle, + activityTitle, true)); } // tRA Test currentActivityPosition = calcPositionNextRight(currentActivityPosition); activityTitle = data.getText("boilerplate.tra.title"); - Integer confidenceLevelsActivityUIID = data.confidenceLevelEnable - ? JsonUtil.optInt(iraActivityJSON, AuthoringJsonTags.ACTIVITY_UIID) - : null; + Integer confidenceLevelsActivityUIID = data.confidenceLevelEnable ? JsonUtil.optInt(iraActivityJSON, + AuthoringJsonTags.ACTIVITY_UIID) : null; Long tRAToolContentId = createScratchieToolContent(userDTO, activityTitle, data.getText("boilerplate.tra.instructions"), false, true, confidenceLevelsActivityUIID, - testQuestionsArray); + qbCollectionUid, testQuestionsArray); activities.add(createScratchieActivity(maxUIID, order++, currentActivityPosition, tRAToolContentId, data.contentFolderID, groupingUIID, null, null, activityTitle)); @@ -218,8 +217,8 @@ String gateTitleBeforeAppEx = data.getText("boilerplate.before.app.ex", new String[] { applicationExercise.title }); if (data.useScheduledGates && data.aeStartOffset != null) { - activities - .add(createScheduledGateActivity(maxUIID, order++, calcGateOffset(currentActivityPosition), + activities.add( + createScheduledGateActivity(maxUIID, order++, calcGateOffset(currentActivityPosition), gateTitleBeforeAppEx, null, data.aeStartOffset, true)); } else { activities.add(createGateActivity(maxUIID, order++, calcGateOffset(currentActivityPosition), @@ -249,10 +248,9 @@ Long aetoolContentId = authoringService.createTblAssessmentToolContent(userDTO, applicationExerciseTitle, - StringUtils.isBlank(applicationExercise.description) - ? data.getText("boilerplate.ae.instructions") - : applicationExercise.description, - null, true, true, false, false, true, true, true, questionsJSONArray); + StringUtils.isBlank(applicationExercise.description) ? data.getText( + "boilerplate.ae.instructions") : applicationExercise.description, null, true, true, + false, false, true, true, true, qbCollectionUid, questionsJSONArray); activities.add(createAssessmentActivity(maxUIID, order++, currentActivityPosition, aetoolContentId, data.contentFolderID, groupingUIID, null, null, applicationExerciseTitle)); } @@ -266,16 +264,18 @@ currentActivityPosition = calcPositionNextRight(currentActivityPosition); String gateTitle = data.getText("boilerplate.before.app.ex.noticeboard", new String[] { noticeboardCountAsString }); - activities.add(createGateActivity(maxUIID, order++, calcGateOffset(currentActivityPosition), - gateTitle, gateTitle, true)); + activities.add( + createGateActivity(maxUIID, order++, calcGateOffset(currentActivityPosition), gateTitle, + gateTitle, true)); } currentActivityPosition = calcPositionNextRight(currentActivityPosition); String notebookTitle = data.getText("boilerplate.grouped.ae.noticeboard.num", new String[] { noticeboardCountAsString }); Long aeNoticeboardContentId = createNoticeboardToolContent(userDTO, notebookTitle, applicationExercise.noticeboardInstructions, null); - activities.add(createNoticeboardActivity(maxUIID, order++, currentActivityPosition, - aeNoticeboardContentId, data.contentFolderID, groupingUIID, null, null, notebookTitle)); + activities.add( + createNoticeboardActivity(maxUIID, order++, currentActivityPosition, aeNoticeboardContentId, + data.contentFolderID, groupingUIID, null, null, notebookTitle)); } } @@ -360,19 +360,19 @@ */ class TBLData extends TemplateData { - /* The error messages are to be sorted by tab, so store them separately and join together */ - List lessonDetailsErrors = new ArrayList<>(); + /* The error messages are to be sorted by tab, so store them separately and join together */ List lessonDetailsErrors = new ArrayList<>(); List ratErrors = new ArrayList<>(); List applicationExerciseErrors = new ArrayList<>(); List peerReviewErrors = new ArrayList<>(); - /* Fields from form */ - String contentFolderID; + /* Fields from form */ String contentFolderID; String sequenceTitle; Integer groupingType; Integer numLearners; Integer numGroups; + Long qbCollectionUid; + Long iraGateStartOffset = null; Long traGateStartOffset = null; Long aeGateStartOffset = null; @@ -420,6 +420,8 @@ numLearners = WebUtil.readIntParam(request, "numLearners", true); numGroups = WebUtil.readIntParam(request, "numGroups", true); + qbCollectionUid = WebUtil.readLongParam(request, "qbCollectionUid", true); + testQuestions = new TreeMap<>(); applicationExercises = new TreeMap<>(); peerReviewCriteria = new TreeMap<>(); @@ -468,8 +470,8 @@ "question" + questionDisplayOrder + "markHedging", false); String title = getTrimmedString(request, "question" + questionDisplayOrder + "title", false); String text = getTrimmedString(request, "question" + questionDisplayOrder, true); - String[] learningOutcomes = request - .getParameterValues("question" + questionDisplayOrder + "learningOutcome"); + String[] learningOutcomes = request.getParameterValues( + "question" + questionDisplayOrder + "learningOutcome"); Long collectionUid = WebUtil.readLongParam(request, "question" + questionDisplayOrder + "collection", true); String uuid = getTrimmedString(request, "question" + questionDisplayOrder + "uuid", false); @@ -541,8 +543,9 @@ String dateTimeString = WebUtil.readStrParam(request, dateField); if (dateTimeString != null) { try { - long offset = TBLTemplateController.LESSON_SCHEDULING_DATETIME_FORMAT.parse(dateTimeString) - .getTime() - this.startTime; + long offset = + TBLTemplateController.LESSON_SCHEDULING_DATETIME_FORMAT.parse(dateTimeString).getTime() + - this.startTime; return offset > 0 ? offset / 60000 : 0; // from ms to minutes } catch (ParseException e) { log.error("Unable to parse date from " + dateField @@ -576,10 +579,8 @@ newAppex.assessments = processAssessments(request, i, newAppex.title); } - if (isDoku - ? StringUtils.isNotBlank(newAppex.description) - || StringUtils.isNotBlank(newAppex.dokuInstructions) - : newAppex.assessments != null) { + if (isDoku ? StringUtils.isNotBlank(newAppex.description) || StringUtils.isNotBlank( + newAppex.dokuInstructions) : newAppex.assessments != null) { newAppex.useNoticeboard = WebUtil.readBooleanParam(request, appexDiv + "NB", false); if (newAppex.useNoticeboard) { newAppex.noticeboardInstructions = getTrimmedString(request, appexDiv + "NBEntry", true); @@ -679,8 +680,8 @@ fieldIndex = name.indexOf("MinWordsLimit"); if (fieldIndex > 0) { Integer criteriaNumber = Integer.valueOf(name.substring(10, fieldIndex)); - findCriteriaInMap(criteriaNumber) - .setCommentsMinWordsLimit(WebUtil.readIntParam(request, name, true)); + findCriteriaInMap(criteriaNumber).setCommentsMinWordsLimit( + WebUtil.readIntParam(request, name, true)); } else { Integer criteriaNumber = Integer.valueOf(name.substring(10)); findCriteriaInMap(criteriaNumber).setTitle(getTrimmedString(request, name, true)); @@ -742,8 +743,8 @@ ArrayNode learningOutcomesJSON = JsonUtil.readArray(learningOutcomes); question.set(RestTags.LEARNING_OUTCOMES, learningOutcomesJSON); } catch (IOException e) { - log.error("Error while processing learning outcomes for question: " - + question.get(RestTags.QUESTION_TITLE)); + log.error("Error while processing learning outcomes for question: " + question.get( + RestTags.QUESTION_TITLE)); } } @@ -766,15 +767,17 @@ Integer correctAnswerDisplay = correctAnswers.get(entry.getKey()); if (correctAnswerDisplay == null) { - Object param = question.has(RestTags.QUESTION_TITLE) ? question.get(RestTags.QUESTION_TITLE) + Object param = question.has(RestTags.QUESTION_TITLE) + ? question.get(RestTags.QUESTION_TITLE) : questionNumber; addValidationErrorMessage("authoring.error.question.correct.num", new Object[] { param }, ratErrors); } else { ArrayNode answers = (ArrayNode) question.get(RestTags.ANSWERS); if (answers == null || answers.size() == 0) { - Object param = question.has(RestTags.QUESTION_TITLE) ? question.get(RestTags.QUESTION_TITLE) + Object param = question.has(RestTags.QUESTION_TITLE) + ? question.get(RestTags.QUESTION_TITLE) : questionNumber; addValidationErrorMessage("authoring.error.question.must.have.answer.num", new Object[] { param }, ratErrors); @@ -789,7 +792,8 @@ } } if (!correctAnswerFound) { - Object param = question.has(RestTags.QUESTION_TITLE) ? question.get(RestTags.QUESTION_TITLE) + Object param = question.has(RestTags.QUESTION_TITLE) + ? question.get(RestTags.QUESTION_TITLE) : questionNumber; addValidationErrorMessage("authoring.error.question.correct.num", new Object[] { param }, ratErrors); @@ -824,14 +828,15 @@ } for (Map.Entry appExEntry : applicationExercises.entrySet()) { AppExData appEx = appExEntry.getValue(); - if (StringUtils.isBlank(appEx.description) && StringUtils.isBlank(appEx.dokuInstructions) - && (appEx.assessments == null)) { + if (StringUtils.isBlank(appEx.description) && StringUtils.isBlank(appEx.dokuInstructions) && ( + appEx.assessments == null)) { addValidationErrorMessage("authoring.error.application.exercise.num", new String[] { "\"" + appEx.title + "\"" }, applicationExerciseErrors); } else if (appEx.assessments != null) { for (Map.Entry assessmentEntry : appEx.assessments.entrySet()) { - assessmentEntry.getValue().validate(applicationExerciseErrors, appBundle, formatter, - appExEntry.getKey(), "\"" + appEx.title + "\"", assessmentEntry.getKey()); + assessmentEntry.getValue() + .validate(applicationExerciseErrors, appBundle, formatter, appExEntry.getKey(), + "\"" + appEx.title + "\"", assessmentEntry.getKey()); } } } @@ -846,4 +851,4 @@ return "/authoring/template/tbl/appex" + (type.equals("doku") ? "Doku" : "Assessment"); } -} +} \ No newline at end of file Index: lams_central/web/authoring/template/tbl/tbl.jsp =================================================================== diff -u -r8a6ef31e47f040438cce78848c3ccb238a9620b7 -rf4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a --- lams_central/web/authoring/template/tbl/tbl.jsp (.../tbl.jsp) (revision 8a6ef31e47f040438cce78848c3ccb238a9620b7) +++ lams_central/web/authoring/template/tbl/tbl.jsp (.../tbl.jsp) (revision f4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a) @@ -9,388 +9,410 @@ - - <%@ include file="../header.jsp" %> - - - - - <fmt:message key="authoring.tbl.template.title"/> - - - + - - - + // this method is run when imported QB question ID is put into itemArea div + function refreshThickbox(){ + // extract the ID + var qbQuestionUid = +$("#itemArea").text(); + if (qbQuestionAddAppexNumber) { + createAssessment('importQbAe', 'numAssessments' + qbQuestionAddAppexNumber, 'divass' + qbQuestionAddAppexNumber, qbQuestionUid, true); + } else { + // fetch HTML with filled data from QB question + createQuestion('numQuestions', 'divq', 'divquestions', 'importQbIra', '&qbQuestionUid=' + qbQuestionUid, true); + } + }; - - + $(document).ready(function(){ + $('[data-toggle="tooltip"]').tooltip(); + }); + + -${fn:toLowerCase('checked') eq 'checked'} + -
- -
-
-
+ + -
- - <%@ include file="../init.jsp" %> + ${fn:toLowerCase('checked') eq 'checked'} -
-
- -
- - - - - +
+ -
-
-
+
+
+
+ + + + <%@ include file="../init.jsp" %> + +
+
+ +
+ + + + + +
+
+
+
- -
- <%@ include file="../grouping.jsp" %> -
-
- + +
+ <%@ include file="../grouping.jsp" %> - - -
-
- -
- - - -
- - -
-
-
+
+
+ + + +
+ +
+ + +
+
+ +
+
+ + + + +
+
+ +
+ + + +
+ + +
+
+
-
- -
- -
-
-
- -
-
+
+ +
+ +
+
+
+ +
+ - -
-
-   - - -
- -
-   - - -
+
+ +
+
+   + + +
+ +
+   + + +
+
+
+ + +
+ +
+
+

+ + + +
+ 1 + <%@ include file="appexAssessment.jsp" %> +
+ + + + +
+ + +
+ + + + +
+
+ 1 + <%@ include file="../tool/peerreviewstar.jsp" %> +
+
+ + +
+
+ +
-
- - -
- -
-
-

- - - -
- 1 - <%@ include file="appexAssessment.jsp" %> -
- - - -
+ - -
- - - - -
-
- 1 - <%@ include file="../tool/peerreviewstar.jsp" %> -
-
- - -
-
- - -
- + -
+ - - - - - - - + + \ No newline at end of file Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -r3cfd9774a258339562d0e13eaa207761028c45c3 -rf4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 3cfd9774a258339562d0e13eaa207761028c45c3) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision f4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a) @@ -3830,7 +3830,7 @@ qbQuestion.setQbOptions(optionList); } - Long collectionUid = JsonUtil.optLong(questionJSONData, RestTags.COLLECTION_UID); + Long collectionUid = JsonUtil.optLong(toolContentJSON, RestTags.COLLECTION_UID); if (collectionUid == null) { // if no collection UUID was specified, questions end up in user's private collection if (privateCollectionUUID == null) { @@ -3839,24 +3839,23 @@ collectionUid = privateCollectionUUID; } - boolean addToCollection = true; + boolean addToCollection = !isModification; // check if it is the same collection - there is a good chance it is - if (collection == null || collectionUid != collection.getUid()) { + if (addToCollection && (collection == null || collectionUid != collection.getUid())) { collection = qbService.getCollection(collectionUid); if (collection == null) { addToCollection = false; } else { collectionUUIDs = qbService.getCollectionQuestions(collection.getUid()).stream() .peek(q -> qbService.releaseFromCache(q)).filter(q -> q.getUuid() != null) .collect(Collectors.mapping(q -> q.getUuid().toString(), Collectors.toSet())); + if (collectionUUIDs.contains(uuid)) { + addToCollection = false; + } } } if (isModification) { - if (collectionUUIDs != null) { - addToCollection &= !collectionUUIDs.contains(uuid); - } - int isModified = qbQuestion.isQbQuestionModified(oldQbQuestion); if (isModified == IQbService.QUESTION_MODIFIED_VERSION_BUMP) { qbQuestion.clearID(); Index: lams_tool_scratchie/lams_tool_scratchie.eml =================================================================== diff -u -r87a8f85eccc8c32edc96754f3a2c8f88f42f3085 -rf4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a --- lams_tool_scratchie/lams_tool_scratchie.eml (.../lams_tool_scratchie.eml) (revision 87a8f85eccc8c32edc96754f3a2c8f88f42f3085) +++ lams_tool_scratchie/lams_tool_scratchie.eml (.../lams_tool_scratchie.eml) (revision f4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a) @@ -1,6 +1,34 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java =================================================================== diff -u -rf05bad0f63a5e9515f10dc19d40bfdd26195ff9f -rf4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision f05bad0f63a5e9515f10dc19d40bfdd26195ff9f) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision f4f2bdc7f40b65cec61ba1d534ca0c5f97b4c84a) @@ -23,30 +23,10 @@ package org.lamsfoundation.lams.tool.scratchie.service; -import java.io.IOException; -import java.security.InvalidParameterException; -import java.sql.Timestamp; -import java.text.SimpleDateFormat; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeSet; -import java.util.UUID; -import java.util.function.Function; -import java.util.stream.Collectors; - +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; @@ -75,37 +55,13 @@ import org.lamsfoundation.lams.qb.service.IQbService; import org.lamsfoundation.lams.rest.RestTags; import org.lamsfoundation.lams.rest.ToolRestManager; -import org.lamsfoundation.lams.tool.ToolCompletionStatus; -import org.lamsfoundation.lams.tool.ToolContentManager; -import org.lamsfoundation.lams.tool.ToolOutput; -import org.lamsfoundation.lams.tool.ToolOutputDefinition; -import org.lamsfoundation.lams.tool.ToolSessionExportOutputData; -import org.lamsfoundation.lams.tool.ToolSessionManager; +import org.lamsfoundation.lams.tool.*; import org.lamsfoundation.lams.tool.exception.DataMissingException; import org.lamsfoundation.lams.tool.exception.ToolException; import org.lamsfoundation.lams.tool.scratchie.ScratchieConstants; -import org.lamsfoundation.lams.tool.scratchie.dao.BurningQuestionLikeDAO; -import org.lamsfoundation.lams.tool.scratchie.dao.ScratchieAnswerVisitDAO; -import org.lamsfoundation.lams.tool.scratchie.dao.ScratchieBurningQuestionDAO; -import org.lamsfoundation.lams.tool.scratchie.dao.ScratchieConfigItemDAO; -import org.lamsfoundation.lams.tool.scratchie.dao.ScratchieDAO; -import org.lamsfoundation.lams.tool.scratchie.dao.ScratchieItemDAO; -import org.lamsfoundation.lams.tool.scratchie.dao.ScratchieSessionDAO; -import org.lamsfoundation.lams.tool.scratchie.dao.ScratchieUserDAO; -import org.lamsfoundation.lams.tool.scratchie.dto.BurningQuestionDTO; -import org.lamsfoundation.lams.tool.scratchie.dto.BurningQuestionItemDTO; -import org.lamsfoundation.lams.tool.scratchie.dto.GroupSummary; -import org.lamsfoundation.lams.tool.scratchie.dto.LeaderResultsDTO; -import org.lamsfoundation.lams.tool.scratchie.dto.OptionDTO; -import org.lamsfoundation.lams.tool.scratchie.dto.ReflectDTO; -import org.lamsfoundation.lams.tool.scratchie.dto.ScratchieItemDTO; -import org.lamsfoundation.lams.tool.scratchie.model.Scratchie; -import org.lamsfoundation.lams.tool.scratchie.model.ScratchieAnswerVisitLog; -import org.lamsfoundation.lams.tool.scratchie.model.ScratchieBurningQuestion; -import org.lamsfoundation.lams.tool.scratchie.model.ScratchieConfigItem; -import org.lamsfoundation.lams.tool.scratchie.model.ScratchieItem; -import org.lamsfoundation.lams.tool.scratchie.model.ScratchieSession; -import org.lamsfoundation.lams.tool.scratchie.model.ScratchieUser; +import org.lamsfoundation.lams.tool.scratchie.dao.*; +import org.lamsfoundation.lams.tool.scratchie.dto.*; +import org.lamsfoundation.lams.tool.scratchie.model.*; import org.lamsfoundation.lams.tool.scratchie.util.ScratchieItemComparator; import org.lamsfoundation.lams.tool.scratchie.web.controller.LearningWebsocketServer; import org.lamsfoundation.lams.tool.service.ICommonScratchieService; @@ -120,16 +76,22 @@ import org.lamsfoundation.lams.util.excel.ExcelRow; import org.lamsfoundation.lams.util.excel.ExcelSheet; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.IOException; +import java.security.InvalidParameterException; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; /** * @author Andrey Balan */ -public class ScratchieServiceImpl implements IScratchieService, ICommonScratchieService, ToolContentManager, - ToolSessionManager, ToolRestManager, IQbToolService { +public class ScratchieServiceImpl + implements IScratchieService, ICommonScratchieService, ToolContentManager, ToolSessionManager, ToolRestManager, + IQbToolService { private static Logger log = Logger.getLogger(ScratchieServiceImpl.class.getName()); private ScratchieDAO scratchieDao; @@ -222,7 +184,8 @@ @Override public ScratchieUser getUserByLoginAndSessionId(String login, long toolSessionId) { List user = scratchieUserDao.findByProperty(User.class, "login", login); - return user.isEmpty() ? null + return user.isEmpty() + ? null : scratchieUserDao.getUserByUserIDAndSessionID(user.get(0).getUserId().longValue(), toolSessionId); } @@ -289,8 +252,8 @@ @Override public void populateItemsWithConfidenceLevels(Long userId, Long toolSessionId, Integer confidenceLevelsActivityUiid, Collection items) { - List confidenceLevelDtos = toolService - .getConfidenceLevelsByActivity(confidenceLevelsActivityUiid, userId.intValue(), toolSessionId); + List confidenceLevelDtos = toolService.getConfidenceLevelsByActivity( + confidenceLevelsActivityUiid, userId.intValue(), toolSessionId); //populate Scratchie items with confidence levels for (ScratchieItem item : items) { @@ -561,7 +524,8 @@ Long toolSessionId = session.getSessionId(); List visitLogsToDelete = new ArrayList<>(); String newPresetMarks = scratchie.getPresetMarks(); - boolean isRecalculateMarks = oldPresetMarks == null ? newPresetMarks != null + boolean isRecalculateMarks = oldPresetMarks == null + ? newPresetMarks != null : newPresetMarks == null || !oldPresetMarks.equals(newPresetMarks); // remove all scratches for modified items @@ -673,8 +637,8 @@ } int numberOfAttempts = ScratchieServiceImpl.getNumberAttemptsForItem(item, visitLogs); - boolean isUnraveledOnFirstAttempt = (numberOfAttempts == 1) - && ScratchieServiceImpl.isItemUnraveled(item, logs); + boolean isUnraveledOnFirstAttempt = + (numberOfAttempts == 1) && ScratchieServiceImpl.isItemUnraveled(item, logs); if (isUnraveledOnFirstAttempt) { count++; } @@ -799,8 +763,7 @@ /* * If isIncludeOnlyLeaders then include the portrait ids needed for monitoring. If false then it * is probably the export and that doesn't need portraits. - */ - public List getMonitoringSummary(Long contentId) { + */ public List getMonitoringSummary(Long contentId) { List groupSummaryList = new ArrayList<>(); List sessions = scratchieSessionDao.getByContentId(contentId); @@ -876,8 +839,8 @@ // -1 if there is no log int attemptNumber = -1; for (ScratchieAnswerVisitLog itemLog : itemLogs) { - if (itemLog.getQbToolQuestion().getUid().equals(item.getUid()) - && isAnswersEqual(item, itemLog.getAnswer(), optionDto.getAnswer())) { + if (itemLog.getQbToolQuestion().getUid().equals(item.getUid()) && isAnswersEqual(item, + itemLog.getAnswer(), optionDto.getAnswer())) { // adding 1 to start from 1 attemptNumber = itemLogs.indexOf(itemLog) + 1; break; @@ -917,8 +880,8 @@ if (QbQuestion.TYPE_MULTIPLE_CHOICE == item.getQbQuestion().getType() || QbQuestion.TYPE_MARK_HEDGING == item.getQbQuestion().getType()) { for (ScratchieAnswerVisitLog userLog : userLogs) { - if (userLog.getQbToolQuestion().getUid().equals(item.getUid()) - && userLog.getQbOption().getUid().equals(optionDto.getQbOptionUid())) { + if (userLog.getQbToolQuestion().getUid().equals(item.getUid()) && userLog.getQbOption().getUid() + .equals(optionDto.getQbOptionUid())) { isScratched = true; break; } @@ -928,8 +891,8 @@ } else { // find according log if it exists for (ScratchieAnswerVisitLog userLog : userLogs) { - if (userLog.getQbToolQuestion().getUid().equals(item.getUid()) - && isAnswersEqual(item, userLog.getAnswer(), optionDto.getAnswer())) { + if (userLog.getQbToolQuestion().getUid().equals(item.getUid()) && isAnswersEqual(item, + userLog.getAnswer(), optionDto.getAnswer())) { isScratched = true; break; } @@ -953,7 +916,7 @@ ScratchieUser leader = scratchieSessionDao.getSessionBySessionId(toolSessionId).getGroupLeader(); Collection assessmentAnswers = scratchie.isAnswersFetchingEnabled() ? toolService.getVsaAnswersFromAssessment(scratchie.getActivityUiidProvidingVsaAnswers(), - leader.getUserId().intValue(), toolSessionId) + leader.getUserId().intValue(), toolSessionId) : null; for (ScratchieItem item : items) { @@ -1006,8 +969,8 @@ OptionDTO optionDto = null; boolean skipAddingUserAnswerToConfidenceLevel = false; for (OptionDTO optionDtoIter : item.getOptionDtos()) { - if (itemQbQuestionUid.equals(optionDtoIter.getQbQuestionUid()) - && isAnswersEqual(item, optionDtoIter.getAnswer(), userLog.getAnswer())) { + if (itemQbQuestionUid.equals(optionDtoIter.getQbQuestionUid()) && isAnswersEqual(item, + optionDtoIter.getAnswer(), userLog.getAnswer())) { optionDto = optionDtoIter; //skip showing ConfidenceLevel, as we already show it due to this user's answer in Assessment skipAddingUserAnswerToConfidenceLevel = optionDtoIter.getUserId() @@ -1033,8 +996,9 @@ ConfidenceLevelDTO confidenceLevelDto = new ConfidenceLevelDTO(); confidenceLevelDto.setUserId(leader.getUserId().intValue()); - String userName = StringUtils.isBlank(leader.getFirstName()) - && StringUtils.isBlank(leader.getLastName()) ? leader.getLoginName() + String userName = + StringUtils.isBlank(leader.getFirstName()) && StringUtils.isBlank(leader.getLastName()) + ? leader.getLoginName() : leader.getFirstName() + " " + leader.getLastName(); confidenceLevelDto.setUserName(userName); confidenceLevelDto.setPortraitUuid(leader.getPortraitId()); @@ -1050,9 +1014,9 @@ * Check if the specified item was unraveled by user * * @param item - * specified item + * specified item * @param userLogs - * uses logs from it (The main reason to have this parameter is to reduce number of queries to DB) + * uses logs from it (The main reason to have this parameter is to reduce number of queries to DB) * @return */ private static boolean isItemUnraveled(ScratchieItem item, List userLogs) { @@ -1064,8 +1028,8 @@ ScratchieAnswerVisitLog log = null; for (ScratchieAnswerVisitLog userLog : userLogs) { - if (userLog.getQbToolQuestion().getUid().equals(item.getUid()) - && userLog.getQbOption().getUid().equals(option.getUid())) { + if (userLog.getQbToolQuestion().getUid().equals(item.getUid()) && userLog.getQbOption().getUid() + .equals(option.getUid())) { log = userLog; break; } @@ -1080,8 +1044,8 @@ } else { List userAnswers = new ArrayList<>(); for (ScratchieAnswerVisitLog userLog : userLogs) { - if (userLog.getQbToolQuestion().getUid().equals(item.getUid()) - && StringUtils.isNotBlank(userLog.getAnswer())) { + if (userLog.getQbToolQuestion().getUid().equals(item.getUid()) && StringUtils.isNotBlank( + userLog.getAnswer())) { userAnswers.add(userLog.getAnswer()); } } @@ -1104,7 +1068,8 @@ break; } // if the option has the highest grade, it is considered the correct one - if (correctAnswersGroup == null ? option.getMaxMark() > 0 + if (correctAnswersGroup == null + ? option.getMaxMark() > 0 : option.getMaxMark() > correctAnswersGroup.getMaxMark()) { correctAnswersGroup = option; } @@ -1135,7 +1100,8 @@ // add mark only if an item was unravelled if (ScratchieServiceImpl.isItemUnraveled(item, userLogs)) { int itemAttempts = ScratchieServiceImpl.getNumberAttemptsForItem(item, userLogs); - String markStr = (itemAttempts <= presetMarks.length) ? presetMarks[itemAttempts - 1] + String markStr = (itemAttempts <= presetMarks.length) + ? presetMarks[itemAttempts - 1] : presetMarks[presetMarks.length - 1]; mark = Double.valueOf(markStr); } @@ -1153,9 +1119,8 @@ // if VS answer was marked as correct in Scratchie after leader chose another answer, // then the subsequent answers do not count if (qbQuestion.getType().equals(QbQuestion.TYPE_VERY_SHORT_ANSWERS)) { - QbOption correctAnswersGroup = qbQuestion.getQbOptions().get(0).isCorrect() - ? qbQuestion.getQbOptions().get(0) - : qbQuestion.getQbOptions().get(1); + QbOption correctAnswersGroup = qbQuestion.getQbOptions().get(0).isCorrect() ? qbQuestion.getQbOptions() + .get(0) : qbQuestion.getQbOptions().get(1); correctVsaOption = correctAnswersGroup.getName(); } for (ScratchieAnswerVisitLog userLog : userLogs) { @@ -1188,8 +1153,8 @@ GroupSummary groupSummary = new GroupSummary(session); List sessionAttempts = scratchieAnswerVisitDao.getLogsBySessionAndItem(sessionId, itemUid); - int numberColumns = options.size() > sessionAttempts.size() || isMcqItem ? options.size() - : sessionAttempts.size(); + int numberColumns = + options.size() > sessionAttempts.size() || isMcqItem ? options.size() : sessionAttempts.size(); groupSummary.setNumberColumns(numberColumns); Map optionMap = new HashMap<>(); @@ -1275,8 +1240,8 @@ Set items = new TreeSet<>(new ScratchieItemComparator()); items.addAll(scratchie.getScratchieItems()); - List burningQuestionDtos = scratchieBurningQuestionDao - .getBurningQuestionsByContentId(scratchie.getUid(), sessionId); + List burningQuestionDtos = scratchieBurningQuestionDao.getBurningQuestionsByContentId( + scratchie.getUid(), sessionId); //in order to group BurningQuestions by items, organise them as a list of BurningQuestionItemDTOs List burningQuestionItemDtos = new ArrayList<>(); @@ -1288,8 +1253,8 @@ ScratchieBurningQuestion burningQuestion = burningQuestionDto.getBurningQuestion(); //general burning question is handled further down - if (!burningQuestion.isGeneralQuestion() - && item.getUid().equals(burningQuestion.getScratchieItem().getUid())) { + if (!burningQuestion.isGeneralQuestion() && item.getUid() + .equals(burningQuestion.getScratchieItem().getUid())) { burningQuestionDtosOfSpecifiedItem.add(burningQuestionDto); } } @@ -1817,7 +1782,8 @@ continue; } row = researchAndAnalysisSheet.initRow(); - String optionTitle = isMcqItem ? removeHtmlMarkup(optionDto.getAnswer()) + String optionTitle = isMcqItem + ? removeHtmlMarkup(optionDto.getAnswer()) : optionDto.getAnswer().strip().replace("\r\n", ", "); if (optionDto.isCorrect()) { optionTitle += "(" + getMessage("label.monitoring.item.summary.correct") + ")"; @@ -1864,8 +1830,8 @@ //build list of all logs left for this item and this session List logsBySessionAndItem = new ArrayList<>(); for (ScratchieAnswerVisitLog log : logs) { - if (log.getSessionId().equals(sessionId) - && log.getQbToolQuestion().getUid().equals(item.getUid())) { + if (log.getSessionId().equals(sessionId) && log.getQbToolQuestion().getUid() + .equals(item.getUid())) { logsBySessionAndItem.add(log); } } @@ -1908,8 +1874,8 @@ int logsBySessionAndItem = 0; for (ScratchieAnswerVisitLog log : logs) { - if (log.getSessionId().equals(sessionId) - && log.getQbToolQuestion().getUid().equals(item.getUid())) { + if (log.getSessionId().equals(sessionId) && log.getQbToolQuestion().getUid() + .equals(item.getUid())) { logsBySessionAndItem++; } } @@ -1969,7 +1935,8 @@ for (OptionDTO option : options) { if (option.isCorrect()) { correctOption = option.getAnswer(); - correctOption = isMcqItem ? removeHtmlMarkup(correctOption) + correctOption = isMcqItem + ? removeHtmlMarkup(correctOption) : correctOption.strip().replace("\r\n", ", "); } } @@ -1995,8 +1962,8 @@ //build list of all logs left for this item and this session List logsBySessionAndItem = new ArrayList<>(); for (ScratchieAnswerVisitLog log : logs) { - if (log.getSessionId().equals(sessionId) - && log.getQbToolQuestion().getUid().equals(itemDto.getUid())) { + if (log.getSessionId().equals(sessionId) && log.getQbToolQuestion().getUid() + .equals(itemDto.getUid())) { logsBySessionAndItem.add(log); } } @@ -2152,16 +2119,17 @@ summary.setMark(numberOfFirstChoiceEvents); // round the percentage cell - String totalPercentage = String - .valueOf((items.size() == 0) ? 0 : (double) numberOfFirstChoiceEvents * 100 / items.size()); + String totalPercentage = String.valueOf( + (items.size() == 0) ? 0 : (double) numberOfFirstChoiceEvents * 100 / items.size()); summary.setTotalPercentage(Double.valueOf(totalPercentage)); } model.put("sessionDtos", groupSummaries); boolean vsaPresent = false; for (ScratchieItem item : itemList) { - item.setCorrectOnFirstAttemptPercent(groupSummaries.isEmpty() ? 0 + item.setCorrectOnFirstAttemptPercent(groupSummaries.isEmpty() + ? 0 : (double) item.getCorrectOnFirstAttemptCount() * 100 / groupSummaries.size()); if (!vsaPresent && item.getQbQuestion().getType().equals(QbQuestion.TYPE_VERY_SHORT_ANSWERS)) { @@ -2233,8 +2201,8 @@ // for displaying purposes if there is no attemps we assign -1 which will be shown as "-" mark = numberOfAttempts == 0 ? -1 : item.getMark(); - isUnraveledOnFirstAttempt = numberOfAttempts == 1 - && ScratchieServiceImpl.isItemUnraveled(item, logs); + isUnraveledOnFirstAttempt = + numberOfAttempts == 1 && ScratchieServiceImpl.isItemUnraveled(item, logs); // find out options' sequential letters - A,B,C... for (ScratchieAnswerVisitLog visitLog : visitLogs) { @@ -2257,8 +2225,10 @@ boolean isCorrect = ScratchieServiceImpl.isItemUnraveledByAnswers(item, List.of(answer)); boolean isFirstAnswersGroupCorrect = qbQuestion.getQbOptions().get(0).isCorrect(); - sequencialLetter = isCorrect && isFirstAnswersGroupCorrect - || !isCorrect && !isFirstAnswersGroupCorrect ? "A" : "B"; + sequencialLetter = + isCorrect && isFirstAnswersGroupCorrect || !isCorrect && !isFirstAnswersGroupCorrect + ? "A" + : "B"; } optionsSequence += optionsSequence.isEmpty() ? sequencialLetter : ", " + sequencialLetter; @@ -2395,9 +2365,9 @@ for (Long newQuestionUid : newQuestionUids) { QbQuestion newQuestion = qbService.getQuestionByUid(newQuestionUid); - if (!(newQuestion.getType().equals(QbQuestion.TYPE_MULTIPLE_CHOICE) - || newQuestion.getType().equals(QbQuestion.TYPE_MARK_HEDGING) - || newQuestion.getType().equals(QbQuestion.TYPE_VERY_SHORT_ANSWERS))) { + if (!(newQuestion.getType().equals(QbQuestion.TYPE_MULTIPLE_CHOICE) || newQuestion.getType() + .equals(QbQuestion.TYPE_MARK_HEDGING) || newQuestion.getType() + .equals(QbQuestion.TYPE_VERY_SHORT_ANSWERS))) { log.warn("QB question with UID " + newQuestionUid + " is not supported by TBL tRAT, skipping"); continue; } @@ -2482,8 +2452,9 @@ public void changeLeaderForGroup(long toolSessionId, long leaderUserId) { ScratchieSession session = getScratchieSessionBySessionId(toolSessionId); if (session.isScratchingFinished()) { - throw new InvalidParameterException("Attempting to assing a new leader with user ID " + leaderUserId - + " to a finished session wtih ID " + toolSessionId); + throw new InvalidParameterException( + "Attempting to assing a new leader with user ID " + leaderUserId + " to a finished session wtih ID " + + toolSessionId); } ScratchieUser existingLeader = session.getGroupLeader(); @@ -2502,8 +2473,9 @@ + toolSessionId); } } else if (!newLeader.getSession().getSessionId().equals(toolSessionId)) { - throw new InvalidParameterException("User with ID " + leaderUserId + " belongs to session with ID " - + newLeader.getSession().getSessionId() + " and not to session with ID " + toolSessionId); + throw new InvalidParameterException( + "User with ID " + leaderUserId + " belongs to session with ID " + newLeader.getSession() + .getSessionId() + " and not to session with ID " + toolSessionId); } session.setGroupLeader(newLeader); @@ -2815,8 +2787,9 @@ } else { log.error("Fail to leave tool Session.Could not find shared scratchie " + "session by given session id: " + toolSessionId); - throw new DataMissingException("Fail to leave tool Session." - + "Could not find shared scratchie session by given session id: " + toolSessionId); + throw new DataMissingException( + "Fail to leave tool Session." + "Could not find shared scratchie session by given session id: " + + toolSessionId); } return toolService.completeToolSession(toolSessionId, learnerId); } @@ -2962,7 +2935,8 @@ return new ToolCompletionStatus(ToolCompletionStatus.ACTIVITY_NOT_ATTEMPTED, null, null); } - return new ToolCompletionStatus(learner.isSessionFinished() ? ToolCompletionStatus.ACTIVITY_COMPLETED + return new ToolCompletionStatus(learner.isSessionFinished() + ? ToolCompletionStatus.ACTIVITY_COMPLETED : ToolCompletionStatus.ACTIVITY_ATTEMPTED, null, null); } // ****************** REST methods ************************* @@ -3030,25 +3004,30 @@ qbQuestion = null; } - Long collectionUid = JsonUtil.optLong(questionData, RestTags.COLLECTION_UID); - if (collectionUid == null) { - // if no collection UUID was specified, questions end up in user's private collection - if (privateCollectionUUID == null) { - privateCollectionUUID = qbService.getUserPrivateCollection(userID).getUid(); + boolean addToCollection = qbQuestion == null; + if (addToCollection) { + Long collectionUid = JsonUtil.optLong(toolContentJSON, RestTags.COLLECTION_UID); + if (collectionUid == null) { + // if no collection UUID was specified, questions end up in user's private collection + if (privateCollectionUUID == null) { + privateCollectionUUID = qbService.getUserPrivateCollection(userID).getUid(); + } + collectionUid = privateCollectionUUID; } - collectionUid = privateCollectionUUID; - } - boolean addToCollection = true; - // check if it is the same collection - there is a good chance it is - if (collection == null || collectionUid != collection.getUid()) { - collection = qbService.getCollection(collectionUid); - if (collection == null) { + // check if it is the same collection - there is a good chance it is + if (collection == null || collectionUid != collection.getUid()) { + collection = qbService.getCollection(collectionUid); + if (collection == null) { + addToCollection = false; + } else { + collectionUUIDs = qbService.getCollectionQuestions(collection.getUid()).stream() + .peek(q -> qbService.releaseFromCache(q)).filter(q -> q.getUuid() != null) + .collect(Collectors.mapping(q -> q.getUuid().toString(), Collectors.toSet())); + } + } + if (collectionUUIDs.contains(uuid)) { addToCollection = false; - } else { - collectionUUIDs = qbService.getCollectionQuestions(collection.getUid()).stream() - .peek(q -> qbService.releaseFromCache(q)).filter(q -> q.getUuid() != null) - .collect(Collectors.mapping(q -> q.getUuid().toString(), Collectors.toSet())); } } @@ -3101,8 +3080,6 @@ userManagementService.save(outcomeMapping); } } - } else if (addToCollection && collectionUUIDs.contains(uuid)) { - addToCollection = false; } item.setQbQuestion(qbQuestion);