Index: lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/ToolContentVersionFilter.java =================================================================== diff -u -r951f0a5804a860ca0b359488d0b7a508d6398ecf -r45f55e8dd35ae8841dbc8db3f38a41d9d52fd2ca --- lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/ToolContentVersionFilter.java (.../ToolContentVersionFilter.java) (revision 951f0a5804a860ca0b359488d0b7a508d6398ecf) +++ lams_common/src/java/org/lamsfoundation/lams/learningdesign/service/ToolContentVersionFilter.java (.../ToolContentVersionFilter.java) (revision 45f55e8dd35ae8841dbc8db3f38a41d9d52fd2ca) @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.function.Consumer; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -166,13 +167,20 @@ * @throws IOException */ public void transformXML(String toolFilePath) throws IOException { + transformXML(toolFilePath, root -> retrieveXML(root)); + } + + /** + * Reads a XML file from given path and applies a transforming method on it + */ + protected void transformXML(String toolFilePath, Consumer converter) throws IOException { File toolFile = new File(toolFilePath); try { DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = docBuilder.parse(new FileInputStream(toolFile)); Element root = doc.getDocumentElement(); - retrieveXML(root); + converter.accept(root); toolFile.renameTo(new File(toolFilePath + "_oldver")); File newToolFile = new File(toolFilePath); @@ -182,7 +190,7 @@ } } - private void retrieveXML(Element root) throws IOException { + private void retrieveXML(Element root) { for (Entry renamed : renamedClassMap.entrySet()) { // if root element was replaced in one of tool.xml files, it needs to be fetched here for further processing root = (Element) ToolContentVersionFilter.renameClass(root, renamed.getKey(), renamed.getValue()); Index: lams_common/src/java/org/lamsfoundation/lams/qb/model/QbQuestion.java =================================================================== diff -u -r06d090eefddaafa108bcd8614d47d3d92f946210 -r45f55e8dd35ae8841dbc8db3f38a41d9d52fd2ca --- lams_common/src/java/org/lamsfoundation/lams/qb/model/QbQuestion.java (.../QbQuestion.java) (revision 06d090eefddaafa108bcd8614d47d3d92f946210) +++ lams_common/src/java/org/lamsfoundation/lams/qb/model/QbQuestion.java (.../QbQuestion.java) (revision 45f55e8dd35ae8841dbc8db3f38a41d9d52fd2ca) @@ -81,7 +81,7 @@ private String description; @Column(name = "max_mark") - private Integer maxMark; + private Integer maxMark = 1; @Column private String feedback; Index: lams_common/src/java/org/lamsfoundation/lams/util/XMLUtil.java =================================================================== diff -u --- lams_common/src/java/org/lamsfoundation/lams/util/XMLUtil.java (revision 0) +++ lams_common/src/java/org/lamsfoundation/lams/util/XMLUtil.java (revision 45f55e8dd35ae8841dbc8db3f38a41d9d52fd2ca) @@ -0,0 +1,42 @@ +package org.lamsfoundation.lams.util; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +public class XMLUtil { + /** + * Finds the first descendant of parentElement with childElementName and returns its text value. + * Otherwise returns defaultValue. + */ + public static String getChildElementValue(Element parentElement, String childElementName, String defaultValue) { + if (parentElement == null || childElementName == null) { + return defaultValue; + } + NodeList nodeList = parentElement.getElementsByTagName(childElementName); + return nodeList.getLength() == 0 ? defaultValue : ((Element) nodeList.item(0)).getTextContent(); + } + + /** + * Find an element in source, takes its value and puts it under a different name in target. + * If no value is found, default value is used. + */ + public static String rewriteTextElement(Element sourceParent, Element targetParent, String sourceElementName, + String targetElementName, String defaultValue) { + String value = XMLUtil.getChildElementValue(sourceParent, sourceElementName, defaultValue); + if (value != null) { + Document document = targetParent.getOwnerDocument(); + Element field = document.createElement(targetElementName); + field.setTextContent(value); + targetParent.appendChild(field); + } + return value; + } + + /** + * Adds an element with value in target parent element. + */ + public static String addTextElement(Element targetParent, String targetElementName, String value) { + return XMLUtil.rewriteTextElement(null, targetParent, null, targetElementName, value); + } +} \ No newline at end of file Index: lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/dbupdates/patch20190809.sql =================================================================== diff -u --- lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/dbupdates/patch20190809.sql (revision 0) +++ lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/dbupdates/patch20190809.sql (revision 45f55e8dd35ae8841dbc8db3f38a41d9d52fd2ca) @@ -0,0 +1,15 @@ +-- Turn off autocommit, so nothing is committed if there is an error +SET AUTOCOMMIT = 0; +SET FOREIGN_KEY_CHECKS=0; +----------------------Put all sql statements below here------------------------- + +--LDEV-4845 Bump version so content version filter kicks in + +UPDATE lams_tool SET tool_version='20190809' WHERE tool_signature='lamc11'; + +----------------------Put all sql statements above here------------------------- + +-- If there were no errors, commit and restore autocommit to on +COMMIT; +SET AUTOCOMMIT = 1; +SET FOREIGN_KEY_CHECKS=1; Index: lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/service/McImportContentVersionFilter.java =================================================================== diff -u -r394f403c289f0fd7808c228840bead5c4e7d5d32 -r45f55e8dd35ae8841dbc8db3f38a41d9d52fd2ca --- lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/service/McImportContentVersionFilter.java (.../McImportContentVersionFilter.java) (revision 394f403c289f0fd7808c228840bead5c4e7d5d32) +++ lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/service/McImportContentVersionFilter.java (.../McImportContentVersionFilter.java) (revision 45f55e8dd35ae8841dbc8db3f38a41d9d52fd2ca) @@ -1,10 +1,19 @@ package org.lamsfoundation.lams.tool.mc.service; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + import org.lamsfoundation.lams.learningdesign.service.ToolContentVersionFilter; import org.lamsfoundation.lams.tool.mc.model.McContent; import org.lamsfoundation.lams.tool.mc.model.McOptsContent; import org.lamsfoundation.lams.tool.mc.model.McQueContent; import org.lamsfoundation.lams.tool.mc.model.McUsrAttempt; +import org.lamsfoundation.lams.util.DateUtil; +import org.lamsfoundation.lams.util.XMLUtil; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; /** * Import filter class for different version of MC content. @@ -57,11 +66,80 @@ public void up20180724To20181202() { this.renameClass("org.lamsfoundation.lams.tool.mc.pojos.", "org.lamsfoundation.lams.tool.mc.model."); } - + /** * Import 20190110 version content to 20190517 version tool server. */ public void up20181202To20190517() { this.removeField(McQueContent.class, "questionHash"); } + + public void up20190517To20190809(String toolFilePath) throws IOException { + // tell which file to process and what to do with its root element + transformXML(toolFilePath, root -> { + Document document = root.getOwnerDocument(); + + // first find questions + NodeList mcQuestions = root.getElementsByTagName("org.lamsfoundation.lams.tool.mc.model.McQueContent"); + if (mcQuestions.getLength() == 0) { + return; + } + + // get create date from MCQ content rather than from each question separately + String createDate = XMLUtil.getChildElementValue(root, "createDate", null); + if (createDate == null) { + createDate = new SimpleDateFormat(DateUtil.EXPORT_LD_FORMAT).format(new Date()); + } + + // go through each question + for (int mcQuestionIndex = 0; mcQuestionIndex < mcQuestions.getLength(); mcQuestionIndex++) { + Element mcQuestion = (Element) mcQuestions.item(mcQuestionIndex); + // create an element for QbQuestion + Element qbQuestion = document.createElement("qbQuestion"); + mcQuestion.appendChild(qbQuestion); + + // transform MCQ data into QB structure + XMLUtil.addTextElement(qbQuestion, "type", "1"); + // Question ID will be filled later as it requires QbService + XMLUtil.addTextElement(qbQuestion, "version", "1"); + XMLUtil.addTextElement(qbQuestion, "createDate", createDate); + XMLUtil.rewriteTextElement(mcQuestion, qbQuestion, "mark", "maxMark", "1"); + XMLUtil.rewriteTextElement(mcQuestion, qbQuestion, "feedback", "feedback", null); + String description = XMLUtil.rewriteTextElement(mcQuestion, qbQuestion, "question", "description", + null); + // get name out of description as there were no descriptions in MCQ before + if (description != null) { + description = description.trim(); + XMLUtil.addTextElement(qbQuestion, "name", + description.substring(0, Math.min(description.length(), 80))); + } + + // now it's time for options + NodeList mcOptions = mcQuestion + .getElementsByTagName("org.lamsfoundation.lams.tool.mc.model.McOptsContent"); + if (mcOptions.getLength() == 0) { + continue; + } + + Element qbOptions = document.createElement("qbOptions"); + qbQuestion.appendChild(qbOptions); + for (int mcOptionIndex = 0; mcOptionIndex < mcQuestions.getLength(); mcOptionIndex++) { + Element mcOption = (Element) mcOptions.item(mcOptionIndex); + Element qbOption = document.createElement("org.lamsfoundation.lams.qb.model.QbOption"); + qbOptions.appendChild(qbOption); + + // MCQ had correct/incorrect indicator. In QB we have 1/0 max mark + boolean correctOption = Boolean + .valueOf(XMLUtil.getChildElementValue(mcOption, "correctOption", "false")); + XMLUtil.addTextElement(qbOption, "maxMark", correctOption ? "1" : "0"); + + XMLUtil.rewriteTextElement(mcOption, qbOption, "displayOrder", "displayOrder", "1"); + XMLUtil.rewriteTextElement(mcOption, qbOption, "mcQueOptionText", "name", null); + } + + // get rid of junk + mcQuestion.removeChild(mcOptions.item(0).getParentNode()); + } + }); + } } \ No newline at end of file Index: lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/service/McService.java =================================================================== diff -u -r1f7c5fb422d619e35598ae416565c790d5c76fff -r45f55e8dd35ae8841dbc8db3f38a41d9d52fd2ca --- lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/service/McService.java (.../McService.java) (revision 1f7c5fb422d619e35598ae416565c790d5c76fff) +++ lams_tool_lamc/src/java/org/lamsfoundation/lams/tool/mc/service/McService.java (.../McService.java) (revision 45f55e8dd35ae8841dbc8db3f38a41d9d52fd2ca) @@ -26,6 +26,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; @@ -1419,10 +1420,29 @@ "Import MC tool content failed. Deserialized object is " + toolPOJO); } McContent toolContentObj = (McContent) toolPOJO; - // reset it to new toolContentId toolContentObj.setMcContentId(toolContentId); toolContentObj.setCreatedBy(newUserUid); + + // we need to save QB questions and options first + for (McQueContent mcQuestion : toolContentObj.getMcQueContents()) { + QbQuestion qbQuestion = mcQuestion.getQbQuestion(); + qbQuestion.setQuestionId(qbService.generateNextQuestionId()); + mcQuestion.setToolContentId(toolContentId); + + Collection qbOptions = new ArrayList<>(qbQuestion.getQbOptions()); + qbQuestion.getQbOptions().clear(); + + mcQueContentDAO.insert(qbQuestion); + + qbQuestion.getQbOptions().addAll(qbOptions); + for (QbOption qbOption : qbOptions) { + qbOption.setQbQuestion(qbQuestion); + mcQueContentDAO.insert(qbOption); + } + qbOptions.clear(); + } + mcContentDAO.saveMcContent(toolContentObj); } catch (ImportToolContentException e) { throw new ToolException(e);