org.sakaiproject.importer.impl.Blackboard6FileParser Maven / Gradle / Ivy
/**
* Copyright (c) 2005-2014 The Apereo Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/ecl2
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sakaiproject.importer.impl;
import java.io.FileInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.sakaiproject.importer.api.ImportFileParser;
import org.sakaiproject.archive.api.ImportMetadata;
import org.sakaiproject.importer.api.Importable;
import org.sakaiproject.importer.impl.importables.Folder;
import org.sakaiproject.importer.impl.importables.HtmlDocument;
import org.sakaiproject.importer.impl.translators.Bb6AnnouncementTranslator;
import org.sakaiproject.importer.impl.translators.Bb6AssessmentAttemptFilesTranslator;
import org.sakaiproject.importer.impl.translators.Bb6AssessmentAttemptTranslator;
import org.sakaiproject.importer.impl.translators.Bb6CollabSessionTranslator;
import org.sakaiproject.importer.impl.translators.Bb6CourseMembershipTranslator;
import org.sakaiproject.importer.impl.translators.Bb6CourseUploadsTranslator;
import org.sakaiproject.importer.impl.translators.Bb6DiscussionBoardTranslator;
import org.sakaiproject.importer.impl.translators.Bb6ExternalLinkTranslator;
import org.sakaiproject.importer.impl.translators.Bb6GroupUploadsTranslator;
import org.sakaiproject.importer.impl.translators.Bb6HTMLDocumentTranslator;
import org.sakaiproject.importer.impl.translators.Bb6QuestionPoolTranslator;
import org.sakaiproject.importer.impl.translators.Bb6SurveyTranslator;
import org.sakaiproject.importer.impl.translators.Bb6TextDocumentTranslator;
import org.sakaiproject.importer.impl.translators.Bb6SmartTextDocumentTranslator;
import org.sakaiproject.importer.impl.translators.Bb6StaffInfoTranslator;
import org.sakaiproject.importer.impl.translators.Bb6AssessmentTranslator;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;
public class Blackboard6FileParser extends IMSFileParser {
public static final String ASSESSMENT_GROUP = "Assessments";
public static final String ANNOUNCEMENT_GROUP = "Announcements";
public static final String ASSESSMENT_FILES_DIRECTORY = "TQimages";
public Blackboard6FileParser() {
// eventually, this will be spring-injected,
// but it's ok to hard-code this for now
addResourceTranslator(new Bb6AnnouncementTranslator());
addResourceTranslator(new Bb6AssessmentTranslator());
addResourceTranslator(new Bb6QuestionPoolTranslator());
addResourceTranslator(new Bb6SurveyTranslator());
addResourceTranslator(new Bb6AssessmentAttemptTranslator());
addResourceTranslator(new Bb6StaffInfoTranslator());
addResourceTranslator(new Bb6HTMLDocumentTranslator());
addResourceTranslator(new Bb6TextDocumentTranslator());
addResourceTranslator(new Bb6SmartTextDocumentTranslator());
addResourceTranslator(new Bb6ExternalLinkTranslator());
addResourceTranslator(new Bb6CollabSessionTranslator());
addResourceTranslator(new Bb6AssessmentAttemptFilesTranslator());
addResourceTranslator(new Bb6CourseUploadsTranslator());
addResourceTranslator(new Bb6GroupUploadsTranslator());
addResourceTranslator(new Bb6CourseMembershipTranslator());
addResourceTranslator(new Bb6DiscussionBoardTranslator());
resourceHelper = new Bb6ResourceHelper();
itemHelper = new Bb6ItemHelper();
fileHelper = new Bb6FileHelper();
manifestHelper = new Bb6ManifestHelper();
}
public boolean isValidArchive(InputStream fileData) {
if (super.isValidArchive(fileData)) {
//TODO check for compliance with IMS 1.1 DTD
Document manifest = extractFileAsDOM("/imsmanifest.xml", fileData);
//String head = extractFileHead("/imsmanifest.xml", fileData);
// originally head.indexOf("xmlns:bb=\"http://www.blackboard.com/content-packaging/\"") >= 0
// but we've already read the whole stream, so have to get it from the document
NodeList nl = manifest.getElementsByTagName("manifest");
if (nl.getLength() > 0) {
Element node = (Element)nl.item(0);
String url = node.getAttribute("xmlns:bb");
if (url == null || !url.equals("http://www.blackboard.com/content-packaging/"))
return false;
} else
return false;
// xmlns:bb means it's v6. However I'm worried about packages generated by other
// software not being quite right. Since we're most concerned about tests, if it
// has 5.5 format tests, let 5.5 deal with it.
return ((XPathHelper.selectNodes("//resource[@type='assessment/x-bb-quiz']", manifest).size() +
XPathHelper.selectNodes("//resource[@type='assessment/x-bb-pool']", manifest).size() +
XPathHelper.selectNodes("//resource[@type='assessment/x-bb-survey']", manifest).size()) == 0 ||
(XPathHelper.selectNodes("//resource[@type='assessment/x-bb-qti-test']", manifest).size() +
XPathHelper.selectNodes("//resource[@type='assessment/x-bb-qti-pool']", manifest).size()) > 0);
// return (XPathHelper.selectNodes("/manifest/organizations/organization/item",manifest).size() > 0
// || XPathHelper.selectNodes("/manifest/resources/resource",manifest).size() > 0);
} else return false;
}
protected boolean isCompoundDocument(Node node, Document resourceDescriptor) {
// the rule we're observing is that any document of type resource/x-bb-document
// that has more than one child will be treated as a compound document
return "resource/x-bb-document".equals(XPathHelper.getNodeValue("./@type",node)) &&
node.hasChildNodes() && (node.getChildNodes().getLength() > 1);
}
protected Importable getCompanionForCompoundDocument(Document resourceDescriptor, Folder folder) {
HtmlDocument html = new HtmlDocument();
StringBuffer content = new StringBuffer();
List fileNodes = XPathHelper.selectNodes("/CONTENT/FILES/FILE", resourceDescriptor);
content.append("\n");
content.append(" " + folder.getTitle() + " \n");
content.append(" \n");
content.append(" " + XPathHelper.getNodeValue("/CONTENT/BODY/TEXT", resourceDescriptor) + "
\n");
content.append(" \n");
for (Node fileNode : fileNodes) {
String fileName = XPathHelper.getNodeValue("./NAME", fileNode);
content.append(" " + fileName + " \n");
}
content.append("
\n");
content.append(" \n");
content.append("");
html.setContent(content.toString());
html.setTitle(folder.getTitle());
html.setContextPath(folder.getPath() + folder.getTitle() + "_manifest");
html.setLegacyGroup(folder.getLegacyGroup());
// we want the html document to come before the folder in sequence
html.setSequenceNum(folder.getSequenceNum() - 1);
return html;
}
protected boolean wantsCompanionForCompoundDocument() {
return true;
}
protected Collection getCategoriesFromArchive(String pathToData) {
Collection categories = new ArrayList();
ImportMetadata im;
Node topLevelItem;
String resourceId;
Node resourceNode;
String targetType;
List topLevelItems = manifestHelper.getTopLevelItemNodes(this.archiveManifest);
for(Iterator i = topLevelItems.iterator(); i.hasNext(); ) {
topLevelItem = (Node)i.next();
// Each course TOC item has a target type.
// At present, we only handle the CONTENT
// and STAFF_INFO target types,
// with assessments and announcements being identified
// separately below.
resourceId = XPathHelper.getNodeValue("./@identifierref", topLevelItem);
resourceNode = manifestHelper.getResourceForId(resourceId, this.archiveManifest);
targetType = XPathHelper.getNodeValue("/COURSETOC/TARGETTYPE/@value", resourceHelper.getDescriptor(resourceNode));
if (!(("CONTENT".equals(targetType)) || ("STAFF_INFO").equals(targetType))) continue;
im = new BasicImportMetadata();
im.setId(itemHelper.getId(topLevelItem));
im.setLegacyTool(itemHelper.getTitle(topLevelItem));
im.setMandatory(false);
im.setFileName(".xml");
im.setSakaiServiceName("ContentHostingService");
im.setSakaiTool("Resources");
categories.add(im);
}
// Figure out if there are assessments
if (XPathHelper.selectNodes("//resource[@type='assessment/x-bb-qti-test']", this.archiveManifest).size()
+ XPathHelper.selectNodes("//resource[@type='assessment/x-bb-qti-pool']", this.archiveManifest).size() > 0) {
im = new BasicImportMetadata();
im.setId("assessments");
im.setLegacyTool(ASSESSMENT_GROUP);
im.setMandatory(false);
im.setFileName(".xml");
im.setSakaiTool("Tests & Quizzes");
categories.add(im);
}
// Figure out if we need an Announcements category
if (XPathHelper.selectNodes("//resource[@type='resource/x-bb-announcement']", this.archiveManifest).size() > 0) {
im = new BasicImportMetadata();
im.setId("announcements");
im.setLegacyTool(ANNOUNCEMENT_GROUP);
im.setMandatory(false);
im.setFileName(".xml");
im.setSakaiTool("Announcements");
categories.add(im);
}
return categories;
}
protected class Bb6ResourceHelper extends ResourceHelper {
public String getTitle(Node resourceNode) {
return resourceNode.getAttributes().getNamedItem("bb:title").getNodeValue().replaceAll("/", "_");
}
public String getType(Node resourceNode) {
String nodeType = XPathHelper.getNodeValue("./@type", resourceNode);
if ("resource/x-bb-document".equals(nodeType)) {
/*
* Since we've gotten a bb-document, we need to figure out what kind it is. Known possible are:
* 1. x-bb-externallink
* 2. x-bb-document
* a. Plain text
* b. Smart text
* c. HTML
* The reason we have to do this is that all the above types are listed as type "resource/x-bb-document"
* in the top level resource node. Their true nature is found with the XML descriptor (.dat file)
*/
if(resourceNode.hasChildNodes()) {
// If it has child-nodes (files, usually) we don't want to parse the actual document
return nodeType;
}
String subType = XPathHelper.getNodeValue("/CONTENT/CONTENTHANDLER/@value", resourceHelper.getDescriptor(resourceNode));
if ("resource/x-bb-externallink".equals(subType)) {
nodeType = "resource/x-bb-externallink";
} else if ("resource/x-bb-asmt-test-link".equals(subType)) {
nodeType = "resource/x-bb-asmt-test-link";
} else {
String docType = XPathHelper.getNodeValue("/CONTENT/BODY/TYPE/@value", resourceHelper.getDescriptor(resourceNode));
if ("H".equals(docType)) {
nodeType = "resource/x-bb-document-html";
} else if ("P".equals(docType)) {
nodeType = "resource/x-bb-document-plain-text";
} else if ("S".equals(docType)) {
nodeType = "resource/x-bb-document-smart-text";
}
}
}
return nodeType;
}
public String getId(Node resourceNode) {
return XPathHelper.getNodeValue("./@identifier", resourceNode);
}
public Document getDescriptor(Node resourceNode) {
try {
String descriptorFilename = resourceNode.getAttributes().getNamedItem("bb:file").getNodeValue();
DocumentBuilder docBuilder;
docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
InputStream fis = new FileInputStream(pathToData + "/" + descriptorFilename);
return (Document) docBuilder.parse(fis);
} catch (Exception e) {
return null;
}
}
public String getDescription(Node resourceNode) {
Document descriptor = resourceHelper.getDescriptor(resourceNode);
return XPathHelper.getNodeValue("/CONTENT/BODY/TEXT", descriptor);
}
public boolean isFolder(Document resourceDescriptor) {
return "true".equals(XPathHelper.getNodeValue("/CONTENT/FLAGS/ISFOLDER/@value", resourceDescriptor));
}
}
protected class Bb6ItemHelper extends ItemHelper {
public String getId(Node itemNode) {
return XPathHelper.getNodeValue("./@identifier", itemNode);
}
public String getTitle(Node itemNode) {
return XPathHelper.getNodeValue("./title",itemNode);
}
public String getDescription(Node itemNode) {
String resourceId = XPathHelper.getNodeValue("./@identifierref", itemNode);
Node resourceNode = manifestHelper.getResourceForId(resourceId, archiveManifest);
return resourceHelper.getDescription(resourceNode);
}
}
protected class Bb6ManifestHelper extends ManifestHelper {
public List getItemNodes(Document manifest) {
return XPathHelper.selectNodes("//item", manifest);
}
public Node getResourceForId(String resourceId, Document manifest) {
return XPathHelper.selectNode("//resource[@identifier='" + resourceId + "']",archiveManifest);
}
public List getResourceNodes(Document manifest) {
return XPathHelper.selectNodes("//resource", manifest);
}
public List getTopLevelItemNodes(Document manifest) {
return XPathHelper.selectNodes("//organization/item", manifest);
}
}
protected class Bb6FileHelper extends FileHelper {
public byte[] getFileBytesForNode(Node node, String contextPath) throws IOException {
//for Bb we ignore the contextPath...
String basePath = XPathHelper.getNodeValue("./@identifier",node.getParentNode());
String fileHref = XPathHelper.getNodeValue("./@href", node).replaceAll("\\\\", "/");
String filePath = basePath + "/" + fileHref;
return getBytesFromFile(new File(pathToData + "/" + filePath));
}
public String getFilePathForNode(Node node, String contextPath) {
// for files that are part of an assessment, we're going to
// tack on an extra container folder to the path.
String parentType = XPathHelper.getNodeValue("../@type", node);
if ("assessment/x-bb-qti-pool".equals(parentType) || "assessment/x-bb-qti-test".equals(parentType)) {
contextPath = contextPath + "/" + ASSESSMENT_FILES_DIRECTORY;
}
String fileHref = XPathHelper.getNodeValue("./@href", node);
return contextPath + "/" + fileHref.replaceAll("\\\\", "/");
}
}
// RU: Needed this for 2.6 -- Cat
public ImportFileParser newParser() {
return new Blackboard6FileParser();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy