/** * RELOAD TOOLS Copyright (c) 2003 Oleg Liber, Bill Olivier, Phillip Beauvoir, * Paul Sharples Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including without * limitation the rights to use, copy, modify, merge, publish, distribute, * sublicense, and/or sell copies of the Software, and to permit persons to whom * the Software is furnished to do so, subject to the following conditions: The * above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS * IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Project * Management Contact: Oleg Liber Bolton Institute of Higher Education Deane * Road Bolton BL3 5AB UK e-mail: o.liber@bolton.ac.uk Technical Contact: * Phillip Beauvoir e-mail: p.beauvoir@bolton.ac.uk Paul Sharples e-mail: * p.sharples@bolton.ac.uk Web: http://www.reload.ac.uk */ package org.olat.modules.scorm.contentpackaging; import java.io.File; import java.io.IOException; import java.util.Iterator; import org.jdom.Document; import org.jdom.Element; import org.jdom.JDOMException; import org.olat.core.logging.OLATRuntimeException; import org.olat.modules.scorm.ISettingsHandler; import org.olat.modules.scorm.server.servermodels.CMI_DataModel; import org.olat.modules.scorm.server.servermodels.SequencerModel; import uk.ac.reload.diva.util.GeneralUtils; import uk.ac.reload.jdom.XMLDocument; import uk.ac.reload.moonunit.contentpackaging.CP_Core; import uk.ac.reload.moonunit.contentpackaging.SCORM12_Core; /** * The ScormPackageHandler Class. A class used to parse a scorm imsmanifest.xml * file and build a xml file for each sco encountered. Each item is examined and * a decision is made about whether or not to generate a sco cmi data model. * * @author Paul Sharples */ public class ScormPackageHandler extends XMLDocument { protected static final String ARCHIVE_EXTENSION = ".zip"; // public static final String XML_EXTENSION = ".xml"; /** * A xml file to hold the state of the course - which items have been * completed etc. */ protected SequencerModel _sequencerModel; /** * A var to flag if any item were found in the manifest. If there are none * found then this package cannot be played and/or is a resource package. */ public boolean _hasItemsToPlay = false; /** * Default org id */ protected String _currentOrgId; /** * Our instance of core scorm methods */ protected SCORM12_Core _scormCore; private ISettingsHandler settings; /** * Default Constructor * * @param manifest * @throws JDOMException * @throws IOException */ public ScormPackageHandler(ISettingsHandler settings) throws JDOMException, IOException { this.settings = settings; // Load the Document loadDocument(settings.getManifestFile()); _sequencerModel = new SequencerModel(new File(settings.getScoItemSequenceFilePath()),settings); _scormCore = new SCORM12_Core(this); } /** * A class to actually do the bulk of the work. It creates an xml file * representing the organizations, similar to the imsmanifest, but also * modelling the sco/asset attributes needed by the runtime system - ie order * of sequence, launch URL... * * @throws NoItemFoundException */ public void buildSettings() throws NoItemFoundException { // get the root element of the manifest // NOTE: CLONE IT first- must work on a copy of the original JDOM doc. Element manifestRoot = (Element) getDocument().getRootElement().clone(); _sequencerModel.setManifestModifiedDate(super.getFile().lastModified()); // now get the organizations node Element orgs = manifestRoot.getChild(CP_Core.ORGANIZATIONS, manifestRoot.getNamespace()); // get the identifier for the default organization Element defaultOrgNode = getDefaultOrganization(orgs); if (defaultOrgNode != null) { // and store the default identifier String defaultOrgIdentifier = defaultOrgNode.getAttributeValue(CP_Core.IDENTIFIER); // set the default organization _sequencerModel.setDefaultOrg(defaultOrgIdentifier); iterateThruManifest(manifestRoot); } else { _sequencerModel.setDefaultOrg(""); } try { _sequencerModel.saveDocument(true); } catch (IOException ex) { throw new OLATRuntimeException(this.getClass(), "Could not save package status.", ex); } // throw an exception if no items were found in the manifest if (!_hasItemsToPlay) { throw new NoItemFoundException(NoItemFoundException.NO_ITEM_FOUND_MSG); } } /** * A method to read through the imsmanifest and build our JDOM model in memory * representing our navigation file. * * @param element */ public void iterateThruManifest(Element element) { String name = element.getName(); if (name.equals(CP_Core.ORGANIZATION) && isDocumentNamespace(element)) { _currentOrgId = element.getAttributeValue(CP_Core.IDENTIFIER); } if (name.equals(CP_Core.ITEM) && isDocumentNamespace(element)) { String id = element.getAttributeValue(CP_Core.IDENTIFIER); String url = ""; String scoType = ""; Element ref_element = getReferencedElement(element); if (ref_element != null) { String ref_name = ref_element.getName(); // A RESOURCE if (ref_name.equals(CP_Core.RESOURCE)) { // get the sco type String theScoType = ref_element.getAttributeValue("scormtype", SCORM12_DocumentHandler.ADLCP_NAMESPACE_12); if (theScoType != null) { scoType = theScoType; } boolean isVisible = true; // check that the item is not hidden String isVisibleAttrib = element.getAttributeValue(CP_Core.ISVISIBLE); if (isVisibleAttrib != null) { if (isVisibleAttrib.equals("false")) { isVisible = false; } } if (!isVisible) { // add this item to the tracking xml file _sequencerModel.addTrackedItem(id, _currentOrgId, SequencerModel.ITEM_COMPLETED); } else { _sequencerModel.addTrackedItem(id, _currentOrgId, SequencerModel.ITEM_NOT_ATTEMPTED); } url = getAbsoluteURL(element); // an item that references somthing has been found.. _hasItemsToPlay = true; if (url.startsWith("file:///")) { String tempHref; if (GeneralUtils.getOS() == GeneralUtils.MACINTOSH || GeneralUtils.getOS() == GeneralUtils.UNIX) { tempHref = url.substring(7, url.length());// mac & linux } else { tempHref = url.substring(8, url.length()); // windows } tempHref = tempHref.replaceAll("%20", " "); // String testHref = // ScormTomcatHandler.getSharedInstance().getScormWebAppPath().toString().replace('\\', // '/'); String testHref = "bla"; testHref = testHref.replaceAll("%20", " "); if (tempHref.startsWith(testHref)) { String localUrlMinusPath = tempHref.substring( // ScormTomcatHandler.getSharedInstance().getScormWebAppPath().toString().length()+1, 3, tempHref.length()); String correctLocalUrl = localUrlMinusPath.replace('\\', '/'); url = correctLocalUrl; } } } // A sub-MANIFEST else if (ref_name.equals(CP_Core.MANIFEST)) { // Get ORGANIZATIONS Element Element orgsElement = ref_element.getChild(CP_Core.ORGANIZATIONS, ref_element.getNamespace()); // Now we have to get the default ORGANIZATION if (orgsElement != null) ref_element = getDefaultOrganization(orgsElement); // Get the children of the referenced <organization> element and graft // clones if (ref_element != null) { Iterator it = ref_element.getChildren().iterator(); while (it.hasNext()) { Element ref_child = (Element) it.next(); element.addContent((Element) ref_child.clone()); } } } } // next we need to find any MAXTIMEALLOWED entries String maxTimeText = ""; Element maxTime = element.getChild(SCORM12_Core.MAXTIMEALLOWED, SCORM12_DocumentHandler.ADLCP_NAMESPACE_12); if (maxTime != null) { maxTimeText = maxTime.getText(); } // next find any TIMELIMITACTION entries String timeLimitText = ""; Element timeLimit = element.getChild(SCORM12_Core.TIMELIMITACTION, SCORM12_DocumentHandler.ADLCP_NAMESPACE_12); if (timeLimit != null) { timeLimitText = timeLimit.getText(); } // next find any DATAFROMLMS entries String datafromLmsText = ""; Element dataFromLms = element.getChild(SCORM12_Core.DATAFROMLMS, SCORM12_DocumentHandler.ADLCP_NAMESPACE_12); if (dataFromLms != null) { datafromLmsText = dataFromLms.getText(); } // next find any MASTERYSCORE entries String masteryScoreText = ""; Element masteryScore = element.getChild(SCORM12_Core.MASTERYSCORE, SCORM12_DocumentHandler.ADLCP_NAMESPACE_12); if (masteryScore != null) { masteryScoreText = masteryScore.getText(); } // if the scoType is "sco", then we have to generate our CMI model // for it... if (scoType.equals(SCORM12_Core.SCO)) { CMI_DataModel scoModel = new CMI_DataModel(settings.getStudentId(), settings.getStudentName(), maxTimeText, timeLimitText, datafromLmsText, masteryScoreText, settings.getLessonMode(), settings.getCreditMode()); scoModel.buildFreshModel(); Document theModel = scoModel.getModel(); File scoFile = settings.getScoDataModelFile(id); scoFile.getParentFile().mkdirs(); scoModel.setDocument(theModel); scoModel.setFile(scoFile); try { scoModel.saveDocument(); } catch (IOException ex) { throw new OLATRuntimeException(this.getClass(), "Could not save sco settings.", ex); } } } Iterator it = element.getChildren().iterator(); while (it.hasNext()) { Element child = (Element) it.next(); iterateThruManifest(child); } } /** * getDefaultOrganization - wraps the same method found in SCORM1_2Core. * * @param orgs * @return - the JDOM element representing the default organization */ public Element getDefaultOrganization(Element orgs) { return _scormCore.getDefaultOrganization(orgs); } /** * getReferencedElement - wraps the same method found in SCORM1_2Core. * * @param sourceElement * @return - the JDOM element representing the resource */ public Element getReferencedElement(Element sourceElement) { return _scormCore.getReferencedElement(sourceElement); } /** * @return The Absolute URL string that an Element references */ public String getAbsoluteURL(Element element) { return _scormCore.getAbsoluteURL(element); } /** * @return True if this is a Manifest that we can handle */ public boolean isSCORM12Manifest() throws DocumentHandlerException { // Has to be a CP Package with a SCORM 1.2 Namespace in there return SCORM12_DocumentHandler.canHandle(getDocument()); } /** * This method checks if the Scorm package has been changed by comparing the * lastModified value of the Scorms "ismanifest" file with the value saved in * the reload-settings.xml. */ public boolean checkIfScormPackageHasChanged() { boolean check = false; if (_sequencerModel != null && _sequencerModel.getManifestModifiedDate() != null) { if (Long.valueOf(_sequencerModel.getManifestModifiedDate()) != super.getFile().lastModified()) { check = true; } } return check; } }