Index: lams_central/src/java/org/lamsfoundation/lams/web/qb/ImsQtiController.java =================================================================== diff -u -re8a7110708b15579af2c6b31ac52a6da427fef6d -r3568517ed841a9c1cfe1b4831ee240929323ca2b --- lams_central/src/java/org/lamsfoundation/lams/web/qb/ImsQtiController.java (.../ImsQtiController.java) (revision e8a7110708b15579af2c6b31ac52a6da427fef6d) +++ lams_central/src/java/org/lamsfoundation/lams/web/qb/ImsQtiController.java (.../ImsQtiController.java) (revision 3568517ed841a9c1cfe1b4831ee240929323ca2b) @@ -4,8 +4,11 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import java.util.UUID; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -39,6 +42,8 @@ public class ImsQtiController { private static Logger log = Logger.getLogger(ImsQtiController.class); + private static final String UUID_LABEL_PREFIX = "lams-qb-uuid-"; + @Autowired @Qualifier("centralMessageService") private MessageService messageService; @@ -58,7 +63,46 @@ @RequestParam String contentFolderID) throws UnsupportedEncodingException { Question[] questions = QuestionParser.parseQuestionChoiceForm(request); + Set collectionUUIDs = null; + for (Question question : questions) { + // UUID in QTI question label is LAMS custom idea + String label = question.getLabel(); + + // try to match the question to an existing QB question in DB + if (label != null && label.startsWith(UUID_LABEL_PREFIX)) { + String uuid = label.substring(UUID_LABEL_PREFIX.length(), label.length()); + + QbQuestion qbQuestion = qbService.getQuestionByUUID(UUID.fromString(uuid)); + if (qbQuestion != null) { + // found an existing question with same UUID + // now check if it is in the collection already + if (collectionUUIDs == null) { + // get UUIDs of collection questions as strings + collectionUUIDs = qbService.getCollectionQuestions(collectionUid).stream() + .filter(q -> q.getUuid() != null) + .collect(Collectors.mapping(q -> q.getUuid().toString(), Collectors.toSet())); + } + + if (collectionUUIDs.contains(uuid)) { + if (log.isDebugEnabled()) { + log.debug("Skipping an existing question. Name: " + qbQuestion.getName() + ", uid: " + + qbQuestion.getUid()); + } + } else { + qbService.addQuestionToCollection(collectionUid, qbQuestion.getQuestionId(), false); + collectionUUIDs.add(uuid); + + if (log.isDebugEnabled()) { + log.debug("Added to collection an existing question. Name: " + qbQuestion.getName() + + ", uid: " + qbQuestion.getUid()); + } + } + continue; + } + + } + QbQuestion qbQuestion = new QbQuestion(); qbQuestion.setName(question.getTitle()); qbQuestion.setDescription(QuestionParser.processHTMLField(question.getText(), false, contentFolderID, @@ -487,6 +531,10 @@ } question.setTitle(qbQuestion.getName()); + if (qbQuestion.getUuid() != null) { + // UUID in QTI question label is LAMS custom idea + question.setLabel(UUID_LABEL_PREFIX + qbQuestion.getUuid()); + } question.setText(qbQuestion.getDescription()); question.setFeedback(qbQuestion.getFeedback()); question.setAnswers(answers); Index: lams_central/web/questions/questionChoice.jsp =================================================================== diff -u -r3f18890e24de43c31ac23d49e2c1a4906e479863 -r3568517ed841a9c1cfe1b4831ee240929323ca2b --- lams_central/web/questions/questionChoice.jsp (.../questionChoice.jsp) (revision 3f18890e24de43c31ac23d49e2c1a4906e479863) +++ lams_central/web/questions/questionChoice.jsp (.../questionChoice.jsp) (revision 3568517ed841a9c1cfe1b4831ee240929323ca2b) @@ -182,6 +182,11 @@ + + + Index: lams_common/src/java/org/lamsfoundation/lams/questions/Question.java =================================================================== diff -u -r7475d08afc280b5e2e5ddf04e8bf35e3166aaf80 -r3568517ed841a9c1cfe1b4831ee240929323ca2b --- lams_common/src/java/org/lamsfoundation/lams/questions/Question.java (.../Question.java) (revision 7475d08afc280b5e2e5ddf04e8bf35e3166aaf80) +++ lams_common/src/java/org/lamsfoundation/lams/questions/Question.java (.../Question.java) (revision 3568517ed841a9c1cfe1b4831ee240929323ca2b) @@ -20,7 +20,6 @@ * **************************************************************** */ - package org.lamsfoundation.lams.questions; import java.util.Arrays; @@ -42,13 +41,14 @@ public static final String QUESTION_TYPE_MATCHING = "mt"; public static final String QUESTION_TYPE_FILL_IN_BLANK = "fb"; public static final String QUESTION_TYPE_MARK_HEDGING = "mh"; - public static final Set QUESTION_TYPES = new TreeSet(Arrays + public static final Set QUESTION_TYPES = new TreeSet<>(Arrays .asList(new String[] { Question.QUESTION_TYPE_MULTIPLE_CHOICE, Question.QUESTION_TYPE_MULTIPLE_RESPONSE, Question.QUESTION_TYPE_TRUE_FALSE, Question.QUESTION_TYPE_ESSAY, Question.QUESTION_TYPE_MATCHING, Question.QUESTION_TYPE_FILL_IN_BLANK, Question.QUESTION_TYPE_MARK_HEDGING })); private String type; private String title; + private String label; private String text; private String feedback; private List answers; @@ -68,6 +68,14 @@ return title; } + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + public void setTitle(String title) { this.title = title; } Index: lams_common/src/java/org/lamsfoundation/lams/questions/QuestionExporter.java =================================================================== diff -u -r62aaf160878735888d077bf28fac3c1989bb8fbd -r3568517ed841a9c1cfe1b4831ee240929323ca2b --- lams_common/src/java/org/lamsfoundation/lams/questions/QuestionExporter.java (.../QuestionExporter.java) (revision 62aaf160878735888d077bf28fac3c1989bb8fbd) +++ lams_common/src/java/org/lamsfoundation/lams/questions/QuestionExporter.java (.../QuestionExporter.java) (revision 3568517ed841a9c1cfe1b4831ee240929323ca2b) @@ -77,7 +77,7 @@ private Document doc = null; private Integer itemId = null; - private Map images = new TreeMap(); + private Map images = new TreeMap<>(); public QuestionExporter(String title, Question[] questions) { if (StringUtils.isBlank(title)) { @@ -161,7 +161,7 @@ * Builds QTI XML file containing structured questions & answers content. * * @return XML file content - * @throws IOException + * @throws IOException */ public String exportQTIFile() throws IOException { itemId = 1000; @@ -213,6 +213,9 @@ private Element exportMultipleChoiceQuestion(Question question) { Element itemElem = doc.createElement("item"); itemElem.setAttribute("title", question.getTitle()); + if (question.getLabel() != null) { + itemElem.setAttribute("label", question.getLabel()); + } itemId++; itemElem.setAttribute("ident", "QUE_" + itemId); @@ -232,7 +235,7 @@ responseLidElem.setAttribute("rtiming", "No"); // question feedback (displayed no matter what answer was choosed) - List feedbackList = new ArrayList(); + List feedbackList = new ArrayList<>(); String correctFeedbackLabel = null; String incorrectFeedbackLabel = null; Element overallFeedbackElem = null; @@ -243,7 +246,7 @@ Element renderChoiceElem = (Element) responseLidElem.appendChild(doc.createElement("render_choice")); short answerId = 0; - List respconditionList = new ArrayList(question.getAnswers().size()); + List respconditionList = new ArrayList<>(question.getAnswers().size()); // iterate through answers, collecting some info along the way for (Answer answer : question.getAnswers()) { @@ -349,6 +352,9 @@ private Element exportMatchingPairsQuestion(Question question) { Element itemElem = doc.createElement("item"); itemElem.setAttribute("title", question.getTitle()); + if (question.getLabel() != null) { + itemElem.setAttribute("label", question.getLabel()); + } itemId++; itemElem.setAttribute("ident", "QUE_" + itemId); @@ -365,8 +371,8 @@ } int answerIndex = 0; - List matchAnswerIdents = new ArrayList(question.getAnswers().size()); - List respconditionElems = new ArrayList(question.getAnswers().size()); + List matchAnswerIdents = new ArrayList<>(question.getAnswers().size()); + List respconditionElems = new ArrayList<>(question.getAnswers().size()); for (Answer answer : question.getAnswers()) { itemId++; String responseLidIdentifier = "QUE_" + itemId + "_RL"; @@ -455,6 +461,9 @@ private Element exportEssayQuestion(Question question) { Element itemElem = doc.createElement("item"); itemElem.setAttribute("title", question.getTitle()); + if (question.getLabel() != null) { + itemElem.setAttribute("label", question.getLabel()); + } itemId++; itemElem.setAttribute("ident", "QUE_" + itemId); @@ -502,6 +511,9 @@ private Element exportFillInBlankQuestion(Question question) { Element itemElem = doc.createElement("item"); itemElem.setAttribute("title", question.getTitle()); + if (question.getLabel() != null) { + itemElem.setAttribute("label", question.getLabel()); + } itemId++; itemElem.setAttribute("ident", "QUE_" + itemId); @@ -628,7 +640,7 @@ Element matimageElem = (Element) materialElem.appendChild(doc.createElement("matimage")); matimageElem.setAttribute("imagtype", imageType); matimageElem.setAttribute("uri", imageName); - + //set image attributes: width, length, and class Matcher attributesMatcher = IMAGE_ATTRIBUTES_PATTERN.matcher(imageTagMatcher.group(0)); while (attributesMatcher.find()) { @@ -648,7 +660,7 @@ appendTextElement(materialElem, text.substring(index)); } } - + /** * Appends mattext element to materialElem. Used in appendMaterialElements(...) method only */ Index: lams_common/src/java/org/lamsfoundation/lams/questions/QuestionParser.java =================================================================== diff -u -r62aaf160878735888d077bf28fac3c1989bb8fbd -r3568517ed841a9c1cfe1b4831ee240929323ca2b --- lams_common/src/java/org/lamsfoundation/lams/questions/QuestionParser.java (.../QuestionParser.java) (revision 62aaf160878735888d077bf28fac3c1989bb8fbd) +++ lams_common/src/java/org/lamsfoundation/lams/questions/QuestionParser.java (.../QuestionParser.java) (revision 3568517ed841a9c1cfe1b4831ee240929323ca2b) @@ -76,13 +76,13 @@ */ public static Question[] parseQTIPackage(InputStream packageFileStream, Set limitType) throws SAXParseException, IOException, SAXException, ParserConfigurationException, ZipFileUtilException { - List result = new ArrayList(); + List result = new ArrayList<>(); // unique folder name String tempPackageName = TEMP_PACKAGE_NAME_PREFIX + System.currentTimeMillis(); String tempPackageDirPath = ZipFileUtil.expandZip(packageFileStream, tempPackageName); try { - List resourceFiles = getQTIResourceFiles(tempPackageDirPath); + List resourceFiles = QuestionParser.getQTIResourceFiles(tempPackageDirPath); if (resourceFiles.isEmpty()) { log.warn("No resource files found in QTI package"); } else { @@ -128,7 +128,7 @@ */ public static Question[] parseQTIFile(InputStream xmlFileStream, String resourcesFolderPath, Set limitType) throws ParserConfigurationException, SAXException, IOException { - List result = new ArrayList(); + List result = new ArrayList<>(); DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = docBuilder.parse(xmlFileStream); @@ -149,8 +149,12 @@ } String questionTitle = questionItem.getAttribute("title"); question.setTitle(questionTitle); + String questionLabel = questionItem.getAttribute("label"); + if (StringUtils.isNotBlank(questionLabel)) { + question.setLabel(questionLabel); + } - Map answerMap = new TreeMap(); + Map answerMap = new TreeMap<>(); Map matchAnswerMap = null; boolean textBasedQuestion = false; @@ -222,7 +226,7 @@ question.setAnswers(new ArrayList()); matchAnswerMap = answerMap; - answerMap = new TreeMap(); + answerMap = new TreeMap<>(); } NodeList responseLidChildrenList = presentationChild.getChildNodes(); @@ -250,7 +254,7 @@ } // extract score and feedback - Map feedbackMap = new TreeMap(); + Map feedbackMap = new TreeMap<>(); if (textBasedQuestion || !answerMap.isEmpty()) { NodeList answerMetadatas = questionItem.getElementsByTagName("respcondition"); // if no answers at all, it is Essay type @@ -367,7 +371,7 @@ * Parses query string (send by form submit or extracted otherwise) from questionChoice.jsp form. */ public static Question[] parseQuestionChoiceForm(HttpServletRequest request) throws UnsupportedEncodingException { - List result = new ArrayList(); + List result = new ArrayList<>(); // to know where to stop searching for question entries (disabled/unchecked form fields are not sent) @@ -381,6 +385,10 @@ if (!StringUtils.isBlank(questionTitle)) { question.setTitle(questionTitle); } + String questionLabel = request.getParameter("question" + questionIndex + "label"); + if (!StringUtils.isBlank(questionLabel)) { + question.setLabel(questionLabel); + } String questionText = request.getParameter("question" + questionIndex + "text"); if (!StringUtils.isBlank(questionText)) { question.setText(questionText); @@ -479,12 +487,12 @@ // find image placeholders while (imageMatcher.find()) { String imageAttributesStr = imageMatcher.group(1); - - List imageAttributes = new ArrayList<>(); + + List imageAttributes = new ArrayList<>(); Collections.addAll(imageAttributes, imageAttributesStr.split("\\|")); String fileName = imageAttributes.get(0); imageAttributes.remove(0); - + // if it is plain text or something goes wrong, the placeholder simply gets removed String replacement = ""; if (!forcePlainText) { @@ -529,7 +537,7 @@ DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document manifest = docBuilder.parse(inputStream); NodeList resourcesList = manifest.getElementsByTagName("resources"); - List resourceFiles = new LinkedList(); + List resourceFiles = new LinkedList<>(); if (resourcesList.getLength() > 0) { Element resources = (Element) resourcesList.item(0); NodeList resourceList = resources.getElementsByTagName("resource"); @@ -584,15 +592,15 @@ String width = ((Element) questionElement).getAttribute("width"); String height = ((Element) questionElement).getAttribute("height"); String classAttr = ((Element) questionElement).getAttribute("entityref"); - + String fileName = ((Element) questionElement).getAttribute("uri"); if (resourcesFolderPath == null) { log.warn("Image " + fileName + " declaration found but its location is unknown"); } else { if (question.getResourcesFolderPath() == null) { question.setResourcesFolderPath(resourcesFolderPath); } - + //add filename and other attributes, separated by ":" character result.append("[IMAGE: ").append(fileName); if (StringUtils.isNotBlank(width)) {