Index: lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java =================================================================== diff -u -r721b93a3650a411424340973612370afff458120 -r9de4a5d9b2c6710a0c8243b90f7ff6be8cf9ae83 --- lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 721b93a3650a411424340973612370afff458120) +++ lams_tool_assessment/src/java/org/lamsfoundation/lams/tool/assessment/service/AssessmentServiceImpl.java (.../AssessmentServiceImpl.java) (revision 9de4a5d9b2c6710a0c8243b90f7ff6be8cf9ae83) @@ -3443,105 +3443,131 @@ Set newQuestionSet = assessment.getQuestions(); // the Assessment constructor will set up the collection for (JsonNode questionJSONData : questions) { - boolean addToCollection = false; + boolean addToCollection = collection != null; AssessmentQuestion question = new AssessmentQuestion(); Integer type = JsonUtil.optInt(questionJSONData, "type"); question.setToolContentId(toolContentID); question.setDisplayOrder(JsonUtil.optInt(questionJSONData, RestTags.DISPLAY_ORDER)); + QbQuestion oldQbQuestion = null; QbQuestion qbQuestion = null; String uuid = JsonUtil.optString(questionJSONData, RestTags.QUESTION_UUID); // try to match the question to an existing QB question in DB if (StringUtils.isNotBlank(uuid)) { - qbQuestion = qbService.getQuestionByUUID(UUID.fromString(uuid)); + oldQbQuestion = qbService.getQuestionByUUID(UUID.fromString(uuid)); } + boolean isModification = oldQbQuestion != null; - if (qbQuestion == null) { - addToCollection = collection != null; - + // are we modifying an existing question or creating a new one + if (isModification) { + qbQuestion = oldQbQuestion.clone(); + qbService.releaseFromCache(oldQbQuestion); + } else { qbQuestion = new QbQuestion(); qbQuestion.setQuestionId(qbService.generateNextQuestionId()); qbQuestion.setContentFolderId(FileUtil.generateUniqueContentFolderID()); - qbQuestion.setType(type); - qbQuestion.setName(JsonUtil.optString(questionJSONData, RestTags.QUESTION_TITLE)); - qbQuestion.setDescription(JsonUtil.optString(questionJSONData, RestTags.QUESTION_TEXT)); + } - qbQuestion.setAllowRichEditor( - JsonUtil.optBoolean(questionJSONData, RestTags.ALLOW_RICH_TEXT_EDITOR, Boolean.FALSE)); - qbQuestion.setAnswerRequired(JsonUtil.optBoolean(questionJSONData, "answerRequired", Boolean.FALSE)); - qbQuestion.setCaseSensitive(JsonUtil.optBoolean(questionJSONData, "caseSensitive", Boolean.FALSE)); - qbQuestion.setCorrectAnswer(JsonUtil.optBoolean(questionJSONData, "correctAnswer", Boolean.FALSE)); - qbQuestion.setMaxMark(JsonUtil.optInt(questionJSONData, "maxMark", 1)); - qbQuestion.setFeedback(JsonUtil.optString(questionJSONData, "feedback")); - qbQuestion.setFeedbackOnCorrect(JsonUtil.optString(questionJSONData, "feedbackOnCorrect")); - qbQuestion.setFeedbackOnIncorrect(JsonUtil.optString(questionJSONData, "feedbackOnIncorrect")); - qbQuestion.setFeedbackOnPartiallyCorrect( - JsonUtil.optString(questionJSONData, "feedbackOnPartiallyCorrect")); - qbQuestion.setMaxWordsLimit(JsonUtil.optInt(questionJSONData, "maxWordsLimit", 0)); - qbQuestion.setMinWordsLimit(JsonUtil.optInt(questionJSONData, "minWordsLimit", 0)); - qbQuestion.setMultipleAnswersAllowed( - JsonUtil.optBoolean(questionJSONData, "multipleAnswersAllowed", Boolean.FALSE)); - qbQuestion.setIncorrectAnswerNullifiesMark( - JsonUtil.optBoolean(questionJSONData, "incorrectAnswerNullifiesMark", Boolean.FALSE)); - qbQuestion.setPenaltyFactor(JsonUtil.optDouble(questionJSONData, "penaltyFactor", 0.0).floatValue()); + qbQuestion.setType(type); + qbQuestion.setName(JsonUtil.optString(questionJSONData, RestTags.QUESTION_TITLE)); + qbQuestion.setDescription(JsonUtil.optString(questionJSONData, RestTags.QUESTION_TEXT)); + qbQuestion.setAllowRichEditor( + JsonUtil.optBoolean(questionJSONData, RestTags.ALLOW_RICH_TEXT_EDITOR, Boolean.FALSE)); + qbQuestion.setAnswerRequired(JsonUtil.optBoolean(questionJSONData, "answerRequired", Boolean.FALSE)); + qbQuestion.setCaseSensitive(JsonUtil.optBoolean(questionJSONData, "caseSensitive", Boolean.FALSE)); + qbQuestion.setCorrectAnswer(JsonUtil.optBoolean(questionJSONData, "correctAnswer", Boolean.FALSE)); + qbQuestion.setMaxMark(JsonUtil.optInt(questionJSONData, "maxMark", 1)); + qbQuestion.setFeedback(JsonUtil.optString(questionJSONData, "feedback")); + qbQuestion.setFeedbackOnCorrect(JsonUtil.optString(questionJSONData, "feedbackOnCorrect")); + qbQuestion.setFeedbackOnIncorrect(JsonUtil.optString(questionJSONData, "feedbackOnIncorrect")); + qbQuestion + .setFeedbackOnPartiallyCorrect(JsonUtil.optString(questionJSONData, "feedbackOnPartiallyCorrect")); + qbQuestion.setMaxWordsLimit(JsonUtil.optInt(questionJSONData, "maxWordsLimit", 0)); + qbQuestion.setMinWordsLimit(JsonUtil.optInt(questionJSONData, "minWordsLimit", 0)); + qbQuestion.setMultipleAnswersAllowed( + JsonUtil.optBoolean(questionJSONData, "multipleAnswersAllowed", Boolean.FALSE)); + qbQuestion.setIncorrectAnswerNullifiesMark( + JsonUtil.optBoolean(questionJSONData, "incorrectAnswerNullifiesMark", Boolean.FALSE)); + qbQuestion.setPenaltyFactor(JsonUtil.optDouble(questionJSONData, "penaltyFactor", 0.0).floatValue()); + + if (!isModification) { // UUID normally gets generated in the DB, but we need it immediately, - // so we generate it programatically. + // so we generate it programmatically. // Re-reading the QbQuestion we just saved does not help as it is read from Hibernate cache, // not from DB where UUID is filed qbQuestion.setUuid(UUID.randomUUID()); assessmentDao.insert(qbQuestion); + } - // Store it back into JSON so Scratchie can read it - // and use the same questions, not create new ones - uuid = qbQuestion.getUuid().toString(); - ObjectNode questionData = (ObjectNode) questionJSONData; - questionData.put(RestTags.QUESTION_UUID, uuid); + if ((type == QbQuestion.TYPE_MATCHING_PAIRS) || (type == QbQuestion.TYPE_MULTIPLE_CHOICE) + || (type == QbQuestion.TYPE_NUMERICAL) || (type == QbQuestion.TYPE_MARK_HEDGING)) { - if ((type == QbQuestion.TYPE_MATCHING_PAIRS) || (type == QbQuestion.TYPE_MULTIPLE_CHOICE) - || (type == QbQuestion.TYPE_NUMERICAL) || (type == QbQuestion.TYPE_MARK_HEDGING)) { + if (!questionJSONData.has(RestTags.ANSWERS)) { + throw new IOException("REST Authoring is missing answers for a question of type " + type + ". Data:" + + toolContentJSON); + } - if (!questionJSONData.has(RestTags.ANSWERS)) { - throw new IOException("REST Authoring is missing answers for a question of type " + type - + ". Data:" + toolContentJSON); + List optionList = new ArrayList<>(); + ArrayNode optionsData = JsonUtil.optArray(questionJSONData, RestTags.ANSWERS); + for (JsonNode answerData : optionsData) { + QbOption option = new QbOption(); + option.setQbQuestion(qbQuestion); + option.setDisplayOrder(JsonUtil.optInt(answerData, RestTags.DISPLAY_ORDER)); + Boolean correct = JsonUtil.optBoolean(answerData, RestTags.CORRECT, null); + if (correct == null) { + Double grade = JsonUtil.optDouble(answerData, "grade"); + option.setMaxMark(grade == null ? 0 : grade.floatValue()); + } else { + option.setMaxMark(correct ? 1 : 0); } + option.setAcceptedError(JsonUtil.optDouble(answerData, "acceptedError", 0.0).floatValue()); + option.setFeedback(JsonUtil.optString(answerData, "feedback")); + option.setName(JsonUtil.optString(answerData, RestTags.ANSWER_TEXT)); + option.setNumericalOption(JsonUtil.optDouble(answerData, "answerFloat", 0.0).floatValue()); + // option.setQuestion(question); can't find the use for this field yet! + optionList.add(option); + } + qbQuestion.setQbOptions(optionList); + } - List optionList = new ArrayList<>(); - ArrayNode optionsData = JsonUtil.optArray(questionJSONData, RestTags.ANSWERS); - for (JsonNode answerData : optionsData) { - QbOption option = new QbOption(); - option.setQbQuestion(qbQuestion); - option.setDisplayOrder(JsonUtil.optInt(answerData, RestTags.DISPLAY_ORDER)); - Boolean correct = JsonUtil.optBoolean(answerData, RestTags.CORRECT, null); - if (correct == null) { - Double grade = JsonUtil.optDouble(answerData, "grade"); - option.setMaxMark(grade == null ? 0 : grade.floatValue()); - } else { - option.setMaxMark(correct ? 1 : 0); - } - option.setAcceptedError(JsonUtil.optDouble(answerData, "acceptedError", 0.0).floatValue()); - option.setFeedback(JsonUtil.optString(answerData, "feedback")); - option.setName(JsonUtil.optString(answerData, RestTags.ANSWER_TEXT)); - option.setNumericalOption(JsonUtil.optDouble(answerData, "answerFloat", 0.0).floatValue()); - // option.setQuestion(question); can't find the use for this field yet! - optionList.add(option); - } - qbQuestion.setQbOptions(optionList); + if (isModification) { + addToCollection &= !collectionUUIDs.contains(uuid); + + int isModified = qbQuestion.isQbQuestionModified(oldQbQuestion); + if (isModified == IQbService.QUESTION_MODIFIED_VERSION_BUMP) { + qbQuestion.clearID(); + qbQuestion.setVersion(qbService.getMaxQuestionVersion(qbQuestion.getQuestionId()) + 1); + qbQuestion.setCreateDate(new Date()); + qbQuestion.setUuid(UUID.randomUUID()); + assessmentDao.insert(qbQuestion); + } else if (isModified == IQbService.QUESTION_MODIFIED_NONE) { + // Changes to question and option content does not count as version bump, + // but rather as update of the original question + // They should probably be marked as "update" rather than "none" + assessmentDao.update(qbQuestion); + } else { + throw new IllegalArgumentException( + "Implement other Question Bank modification levels in Assessment tool"); } - } else if (collection != null && !collectionUUIDs.contains(uuid)) { - addToCollection = true; } + // Store it back into JSON so Scratchie can read it + // and use the same questions, not create new ones + uuid = qbQuestion.getUuid().toString(); + ObjectNode questionData = (ObjectNode) questionJSONData; + questionData.put(RestTags.QUESTION_UUID, uuid); + // question.setUnits(units); Needed for numerical type question question.setQbQuestion(qbQuestion); checkType(question.getType()); newQuestionSet.add(question); // all questions need to end up in user's private collection if (addToCollection) { - qbService.addQuestionToCollection(collection.getUid(), qbQuestion.getQuestionId(), false); + // qbService.addQuestionToCollection(collection.getUid(), qbQuestion.getQuestionId(), false); collectionUUIDs.add(uuid); } }