/**
* 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.server.servermodels;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.jdom.Comment;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Parent;
import org.olat.core.logging.OLATRuntimeException;
import org.olat.core.logging.Tracing;
import org.olat.modules.scorm.ISettingsHandler;
import uk.ac.reload.jdom.XMLDocument;
/**
* This class is responsible for creating the xml file which persists the
* current state of the package
*
* @author Paul Sharples
*/
public class SequencerModel extends XMLDocument {
protected static final String ROOT_NODE_NAME = "navigation";
protected static final String ORG_NODE = "organization";
protected static final String ITEM_NODE = "item";
protected static final String ITEM_IDENTIFIER = "id";
/**
* Comment for <code>ITEM_NOT_ATTEMPTED</code>
*/
public static final String ITEM_NOT_ATTEMPTED = "not attempted";
/**
* Comment for <code>ITEM_COMPLETED</code>
*/
public static final String ITEM_COMPLETED = "completed";
protected static final String ITEM_INCOMPLETE = "incomplete";
/**
* Comment for <code>ITEM_PASSED</code>
*/
public static final String ITEM_PASSED = "passed";
protected static final String ITEM_FAILED = "failed";
protected static final String MANIFEST_MODIFIED = "manifest_last_modified";
/**
* A vector used to house which items have been added to the navigation - used
* to determine which nodes have been deleted since last import
*/
Vector _items = new Vector();
/**
* Our unique signature
*/
static final String[] scorm_comments = { "This is a version SCORM 1.2 Sequencer Model",
"Spawned from Reload Scorm Player - http://www.reload.ac.uk" };
/**
* Constructor takes a file (the nav xml file ref) and will try to load it
* either as new file or an existing file
*
* @param file
*/
public SequencerModel(File file, ISettingsHandler settings) {
if (!file.exists()) {
super.setFile(file);
init();
} else {
try {
super.loadDocument(file);
} catch (JDOMException ex) {
throw new OLATRuntimeException(this.getClass(), "JDOM Exception trying to load sequencer model", ex);
} catch (IOException ex) {
throw new OLATRuntimeException(this.getClass(), "Could not load sequencer model", ex);
}
}
}
/**
* @param orgName
*/
public void setDefaultOrg(String orgName) {
getDocument().getRootElement().setAttribute("default", orgName);
}
/**
* @return the default organisation
*/
public String getDefaultOrg() {
return getDocument().getRootElement().getAttributeValue("default");
}
/**
* @param lastModified
*/
public void setManifestModifiedDate(long lastModified) {
Element root = getDocument().getRootElement();
if (root.getChild(MANIFEST_MODIFIED) == null) {
Element time = new Element(MANIFEST_MODIFIED);
time.setText(Long.toString(lastModified));
root.addContent(time);
} else {
root.getChild(MANIFEST_MODIFIED).setText(Long.toString(lastModified));
}
}
/**
* @return the modification date
*/
public String getManifestModifiedDate() {
return getDocument().getRootElement().getChild(MANIFEST_MODIFIED).getText();
}
/**
* Method to return all of the items found during a parse of the manifest
*
* @return all items of the manifest
*/
protected String[] getItems() {
String[] legitimateIds = new String[_items.size()];
_items.copyInto(legitimateIds);
return legitimateIds;
}
/**
* Method to commit the navigation xml file to disk
*
* @param sco
* @param status
*/
public void updateDiskModel(String sco, String status) {
List itemList = getDocument().getRootElement().getChildren(ITEM_NODE);
Iterator itemListElement = itemList.iterator();
while (itemListElement.hasNext()) {
Element anItem = (Element) itemListElement.next();
if (anItem.getAttributeValue(ITEM_IDENTIFIER).equals(sco)) {
anItem.setText(status);
}
}
try {
saveDocument();
} catch (IOException ex) {
throw new OLATRuntimeException(this.getClass(), "could not save sequencer model.", ex);
}
}
/**
* Method to get all item identifiers under a given organization
*
* @param org
* @return - has table with idenitifer and current status
*/
public Hashtable getItemsAsHash(String org) {
Hashtable hash = new Hashtable();
List itemList = getDocument().getRootElement().getChildren(ITEM_NODE);
Iterator itemListElement = itemList.iterator();
while (itemListElement.hasNext()) {
Element anItem = (Element) itemListElement.next();
hash.put(anItem.getAttributeValue(ITEM_IDENTIFIER), anItem.getText());
}
return hash;
}
/**
* Initilise this JDOM doc - adding comment and setting root node
*/
protected void init() {
Document _model;
Element root = new Element(ROOT_NODE_NAME);
_model = new Document(root);
for (int i = 0; i < scorm_comments.length; i++) {
Comment comment = new Comment(scorm_comments[i]);
_model.getContent().add(0, comment);
}
this.setDocument(_model);
}
/**
* Method to add a new item (or update an existing item) to the navigation
* file - used to persist package status
*
* @param itemId
* @param orgId
* @param value
*/
public void addTrackedItem(String itemId, String orgId, String value) {
boolean itemFound = false;
// check to see if the item is there already, if so reset it
List itemList = getDocument().getRootElement().getChildren(ITEM_NODE);
if (itemList != null && !itemList.isEmpty()) {
Iterator itemListElement = itemList.iterator();
while (itemListElement.hasNext()) {
Element anItem = (Element) itemListElement.next();
if (anItem.getAttributeValue(ITEM_IDENTIFIER).equals(itemId)) {
anItem.setText(value);
_items.add(itemId);
itemFound = true;
}
}
}
// otherwise add it as a new node.
if (itemFound == false) {
Element node = new Element(ITEM_NODE);
node.setText(value);
node.setAttribute(ITEM_IDENTIFIER, itemId);
node.setAttribute(ORG_NODE, orgId);
getDocument().getRootElement().addContent(node);
_items.add(itemId);
}
}
/**
* Overide super method so we can decide if we want to cleanup the JDOM tree
* first - default - no cleanup
*
* @throws IOException
*/
public void saveDocument() throws IOException {
saveDocument(false);
}
/**
* @param cleanUp - a boolean to decide if the JDOM tree needs to be validated
* @throws IOException
*/
public void saveDocument(boolean cleanUp) throws IOException {
if (cleanUp) {
removeOldNodes(getItems());
}
super.saveDocument();
_items.clear();
}
/**
* Method to determine if the navigation file has changed since it was last
* imported. If so then we need to check for old <items> and remove them from
* the document
*
* @param items
*/
protected void removeOldNodes(String[] items) {
Vector v = new Vector();
List itemList = getDocument().getRootElement().getChildren(ITEM_NODE);
if (itemList != null && !itemList.isEmpty()) {
Iterator itemListElement = itemList.iterator();
while (itemListElement.hasNext()) {
Element anItem = (Element) itemListElement.next();
if (!doesItemExist(anItem.getAttributeValue(ITEM_IDENTIFIER), items)) {
// mark this node - (can't delete at the same time as looping
// otherwise get concurrent access errors)
v.add(anItem);
}
}
Iterator itemsToRemove = v.iterator();
while (itemsToRemove.hasNext()) {
Element anItemToDelete = (Element) itemsToRemove.next();
Parent parent = anItemToDelete.getParent();
if (parent != null) {
Tracing.logWarn("item no longer exists so remove " + anItemToDelete.getAttributeValue(ITEM_IDENTIFIER),null,SequencerModel.class);
parent.removeContent(anItemToDelete);
}
}
}
v.clear();
}
/**
* Utility method which takes a string(item) and a string array The method
* searches the string array for the value in item, if it exists returns true,
* otherwise false.
*
* @param item
* @param legitimateItems
* @return true is item exists
*/
protected boolean doesItemExist(String item, String[] legitimateItems) {
for (int i = 0; i < legitimateItems.length; i++) {
if (legitimateItems[i].equals(item)) { return true; }
}
return false;
}
}