All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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(" \n"); } content.append("
" + fileName + "
\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