Index: lams_common/src/java/org/lamsfoundation/lams/util/XMLUtil.java =================================================================== diff -u -r78b1835bd2936b2de7d98c7eff33621fa9f5e656 -rb765f567ba3ca9ca048b434d243d94d865ef363c --- lams_common/src/java/org/lamsfoundation/lams/util/XMLUtil.java (.../XMLUtil.java) (revision 78b1835bd2936b2de7d98c7eff33621fa9f5e656) +++ lams_common/src/java/org/lamsfoundation/lams/util/XMLUtil.java (.../XMLUtil.java) (revision b765f567ba3ca9ca048b434d243d94d865ef363c) @@ -1,11 +1,18 @@ package org.lamsfoundation.lams.util; import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.function.Function; import org.apache.commons.lang.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; import org.w3c.dom.NodeList; public class XMLUtil { @@ -17,12 +24,12 @@ if (parentElement == null || childElementName == null) { return defaultValue; } - NodeList nodeList = parentElement.getElementsByTagName(childElementName); - if (nodeList.getLength() == 0) { + List foundElements = XMLUtil.findChildren(parentElement, childElementName); + if (foundElements.size() == 0) { return defaultValue; } - if (nodeList.getLength() == 1) { - return nodeList.item(0).getTextContent(); + if (foundElements.size() == 1) { + return foundElements.get(0).getTextContent(); } throw new InvalidParameterException( "There is more than one element with name \"" + childElementName + "\" in its parent"); @@ -35,40 +42,47 @@ public static String rewriteTextElement(Element sourceParent, Element targetParent, String sourceElementName, String targetElementName, String defaultValue) { return XMLUtil.rewriteTextElement(sourceParent, targetParent, sourceElementName, targetElementName, - defaultValue, false); + defaultValue, false, false); } /** * 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. + * Attributes can be copied to the target element, depending on the parameter. * The source source element can be removed, depending on the parameter. - */ - public static String rewriteTextElement(Element sourceParent, Element targetParent, String sourceElementName, - String targetElementName, String defaultValue, boolean removeSource) { - return XMLUtil.rewriteTextElement(sourceParent, targetParent, sourceElementName, targetElementName, - defaultValue, removeSource, (Function[]) null); - } - - /** - * 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. - * The source source element can be removed, depending on the parameter. * If valueConverter function is provided, it is applied on value or defaultValue before setting it in target. */ @SafeVarargs public static String rewriteTextElement(Element sourceParent, Element targetParent, String sourceElementName, - String targetElementName, String defaultValue, boolean removeSource, + String targetElementName, String defaultValue, boolean preserveAttributes, boolean removeSource, Function... valueConverters) { - String value = XMLUtil.getChildElementValue(sourceParent, sourceElementName, defaultValue); - if (removeSource) { - if (sourceParent == null || sourceElementName == null) { - throw new InvalidParameterException("Can not remove an element if its name or parent are null"); + String value = defaultValue; + Element foundElement = null; + // find the element we are looking for + List foundElements = XMLUtil.findChildren(sourceParent, sourceElementName); + if (foundElements.size() == 1) { + foundElement = foundElements.get(0); + value = foundElement.getTextContent(); + } else if (foundElements.size() > 1) { + throw new InvalidParameterException( + "There is more than one element with name \"" + sourceElementName + "\" in its parent"); + } + // if we start with working with target elements, removing source and working with attributes is broken + // so first prepare data, modify DOM and only then work with target + Map attributes = null; + if (preserveAttributes && foundElement != null) { + NamedNodeMap nodeMap = foundElement.getAttributes(); + if (nodeMap.getLength() > 0) { + attributes = new HashMap<>(); + for (int attributeIndex = 0; attributeIndex < nodeMap.getLength(); attributeIndex++) { + Node attribute = nodeMap.item(attributeIndex); + attributes.put(attribute.getNodeName(), attribute.getTextContent()); + } } - NodeList nodeList = sourceParent.getElementsByTagName(sourceElementName); - if (nodeList.getLength() == 1) { - sourceParent.removeChild(nodeList.item(0)); - } } + if (removeSource && foundElement != null) { + sourceParent.removeChild(foundElement); + } if (StringUtils.isNotBlank(value)) { Document document = targetParent.getOwnerDocument(); Element field = document.createElement(targetElementName); @@ -78,6 +92,11 @@ } } field.setTextContent(value); + if (attributes != null) { + for (Entry attributeEntry : attributes.entrySet()) { + field.setAttribute(attributeEntry.getKey(), attributeEntry.getValue()); + } + } targetParent.appendChild(field); } return value; @@ -105,4 +124,25 @@ throw new InvalidParameterException( "There is more than one element with name \"" + childElementName + "\" in its parent"); } + + /** + * Finds immediate children of a given element with given name. + */ + public static List findChildren(Element parentElement, String childElementName) { + List result = new ArrayList<>(); + if (parentElement == null || childElementName == null) { + return result; + } + NodeList nodeList = parentElement.getChildNodes(); + for (int childIndex = 0; childIndex < nodeList.getLength(); childIndex++) { + Node childNode = nodeList.item(childIndex); + if (childNode instanceof Element) { + Element childElement = (Element) childNode; + if (childElement.getTagName().equalsIgnoreCase(childElementName)) { + result.add(childElement); + } + } + } + return result; + } } \ No newline at end of file Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/dbupdates/patch20190809.sql =================================================================== diff -u --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/dbupdates/patch20190809.sql (revision 0) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/dbupdates/patch20190809.sql (revision b765f567ba3ca9ca048b434d243d94d865ef363c) @@ -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='lascrt11'; + +----------------------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_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/scratchieApplicationContext.xml =================================================================== diff -u -rfba7287887f6dd83d3098100af6320cccf1f3e36 -rb765f567ba3ca9ca048b434d243d94d865ef363c --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/scratchieApplicationContext.xml (.../scratchieApplicationContext.xml) (revision fba7287887f6dd83d3098100af6320cccf1f3e36) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/scratchieApplicationContext.xml (.../scratchieApplicationContext.xml) (revision b765f567ba3ca9ca048b434d243d94d865ef363c) @@ -113,6 +113,9 @@ + + + Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieImportContentVersionFilter.java =================================================================== diff -u -r23980a8cbd4e0d8d4e0b7ac4ad02e510d5653b75 -rb765f567ba3ca9ca048b434d243d94d865ef363c --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieImportContentVersionFilter.java (.../ScratchieImportContentVersionFilter.java) (revision 23980a8cbd4e0d8d4e0b7ac4ad02e510d5653b75) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieImportContentVersionFilter.java (.../ScratchieImportContentVersionFilter.java) (revision b765f567ba3ca9ca048b434d243d94d865ef363c) @@ -20,15 +20,24 @@ * **************************************************************** */ - package org.lamsfoundation.lams.tool.scratchie.service; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + import org.lamsfoundation.lams.learningdesign.service.ToolContentVersionFilter; +import org.lamsfoundation.lams.qb.QbUtils; import org.lamsfoundation.lams.tool.scratchie.dto.QbOptionDTO; import org.lamsfoundation.lams.tool.scratchie.model.Scratchie; 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.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 Scratchie content. @@ -75,7 +84,7 @@ public void up20140613To20150206() { this.addField(Scratchie.class, "burningQuestionsEnabled", "1"); } - + /** * Import 20140613 version content to 20150206 version tool server. */ @@ -85,4 +94,74 @@ this.removeField(ScratchieItem.class, "userMark"); this.removeField(ScratchieItem.class, "userAttempts"); } + + public void up20190103To20190809(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 scratchieQuestions = root + .getElementsByTagName("org.lamsfoundation.lams.tool.scratchie.model.ScratchieItem"); + if (scratchieQuestions.getLength() == 0) { + return; + } + + // go through each question + for (int scratchieQuestionIndex = 0; scratchieQuestionIndex < scratchieQuestions + .getLength(); scratchieQuestionIndex++) { + Element scratchieQuestion = (Element) scratchieQuestions.item(scratchieQuestionIndex); + // create an element for QbQuestion + Element qbQuestion = document.createElement("qbQuestion"); + scratchieQuestion.appendChild(qbQuestion); + + // transform Scratchie 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.rewriteTextElement(scratchieQuestion, qbQuestion, "createDate", "createDate", + new SimpleDateFormat(DateUtil.EXPORT_LD_FORMAT).format(new Date()), true, true); + XMLUtil.rewriteTextElement(scratchieQuestion, qbQuestion, "title", "name", null, false, true, + QbUtils.QB_MIGRATION_CKEDITOR_CLEANER, QbUtils.QB_MIGRATION_TAG_CLEANER); + XMLUtil.rewriteTextElement(scratchieQuestion, qbQuestion, "description", "description", null, false, + true, QbUtils.QB_MIGRATION_CKEDITOR_CLEANER, QbUtils.QB_MIGRATION_TRIMMER); + XMLUtil.rewriteTextElement(scratchieQuestion, scratchieQuestion, "orderId", "displayOrder", null, false, + true); + + // now it's time for options + NodeList scratchieOptions = scratchieQuestion + .getElementsByTagName("org.lamsfoundation.lams.tool.scratchie.model.ScratchieAnswer"); + if (scratchieOptions.getLength() == 0) { + continue; + } + + Element qbOptions = document.createElement("qbOptions"); + qbQuestion.appendChild(qbOptions); + int maxDisplayOrder = 0; + for (int scratchieOptionIndex = 0; scratchieOptionIndex < scratchieOptions + .getLength(); scratchieOptionIndex++) { + Element scratchieOption = (Element) scratchieOptions.item(scratchieOptionIndex); + Element qbOption = document.createElement("org.lamsfoundation.lams.qb.model.QbOption"); + qbOptions.appendChild(qbOption); + + // Scratchie had correct/incorrect indicator. In QB we have 1/0 max mark + boolean correctOption = Boolean + .valueOf(XMLUtil.getChildElementValue(scratchieOption, "correct", "false")); + XMLUtil.addTextElement(qbOption, "maxMark", correctOption ? "1" : "0"); + + maxDisplayOrder = Math.max( + Integer.valueOf(XMLUtil.rewriteTextElement(scratchieOption, qbOption, "orderId", + "displayOrder", String.valueOf(maxDisplayOrder + 1), false, true)), + maxDisplayOrder); + + XMLUtil.rewriteTextElement(scratchieOption, qbOption, "description", "name", null, false, true, + QbUtils.QB_MIGRATION_CKEDITOR_CLEANER); + } + + // get rid of junk + scratchieQuestion.removeChild(scratchieOptions.item(0).getParentNode()); + XMLUtil.removeElement(scratchieQuestion, "isCreateByAuthor"); + } + }); + } } Index: lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java =================================================================== diff -u -rf1bd855c28fc7140f19ba963adfe51bf78157ce1 -rb765f567ba3ca9ca048b434d243d94d865ef363c --- lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision f1bd855c28fc7140f19ba963adfe51bf78157ce1) +++ lams_tool_scratchie/src/java/org/lamsfoundation/lams/tool/scratchie/service/ScratchieServiceImpl.java (.../ScratchieServiceImpl.java) (revision b765f567ba3ca9ca048b434d243d94d865ef363c) @@ -60,6 +60,7 @@ import org.lamsfoundation.lams.qb.model.QbOption; import org.lamsfoundation.lams.qb.model.QbQuestion; import org.lamsfoundation.lams.qb.model.QbToolQuestion; +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; @@ -150,6 +151,8 @@ private IEventNotificationService eventNotificationService; + private IQbService qbService; + private ScratchieOutputFactory scratchieOutputFactory; // ******************************************************************************* @@ -1912,7 +1915,28 @@ user.setUserId(newUserUid.longValue()); } + // we need to save QB questions and options first + for (ScratchieItem scratchieItem : toolContentObj.getScratchieItems()) { + QbQuestion qbQuestion = scratchieItem.getQbQuestion(); + qbQuestion.setQuestionId(qbService.generateNextQuestionId()); + + Collection qbOptions = new ArrayList<>(qbQuestion.getQbOptions()); + qbQuestion.getQbOptions().clear(); + + scratchieDao.insert(qbQuestion); + + qbQuestion.getQbOptions().addAll(qbOptions); + for (QbOption qbOption : qbOptions) { + qbOption.setQbQuestion(qbQuestion); + scratchieDao.insert(qbOption); + } + qbOptions.clear(); + + scratchieDao.insert(scratchieItem); + } + scratchieDao.saveObject(toolContentObj); + } catch (ImportToolContentException e) { throw new ToolException(e); } @@ -2172,6 +2196,10 @@ this.eventNotificationService = eventNotificationService; } + public void setQbService(IQbService qbService) { + this.qbService = qbService; + } + @Override public String getMessage(String key) { return messageService.getMessage(key);