/*
* Open Source Physics software is free software as described near the bottom of this code file.
*
* For additional information and documentation on Open Source Physics please see:
* <http://www.opensourcephysics.org/>
*/
package org.opensourcephysics.tools;
import java.io.File;
import java.net.URL;
import java.util.*;
import javax.xml.parsers.*;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
/**
* This provides static methods for getting a LibraryCollection from ComPADRE.
* Adapted from code written for EJS by Francisco Esquembre.
*
* @author Francisco Esquembre, Douglas Brown
* @version 1.0
*/
@SuppressWarnings("javadoc")
public class LibraryComPADRE {
public static final String OSP_INFO_URL="http://www.compadre.org/OSP/online_help/EjsDL/OSPCollection.html"; //$NON-NLS-1$
public static final String EJS_SERVER_TREE="http://www.compadre.org/osp/services/REST/osp_jars.cfm?verb=Identify&OSPType=EJS%20Model&AttachedDocument=Source%20Code"; //$NON-NLS-1$
public static final String EJS_SERVER_RECORDS="http://www.compadre.org/osp/services/REST/osp_jars.cfm?OSPType=EJS%20Model&AttachedDocument=Source%20Code"; //$NON-NLS-1$
public static final String EJS_COLLECTION_NAME="EJS OSP Collection"; //$NON-NLS-1$
public static final String EJS_INFO_URL="http://www.compadre.org/OSP/online_help/EjsDL/DLModels.html"; //$NON-NLS-1$
public static final String TRACKER_SERVER_TREE="http://www.compadre.org/osp/services/REST/osp_tracker.cfm?verb=Identify&OSPType=Tracker"; //$NON-NLS-1$
public static final String TRACKER_SERVER_RECORDS="http://www.compadre.org/osp/services/REST/osp_tracker.cfm?OSPType=Tracker"; //$NON-NLS-1$
public static final String TRACKER_COLLECTION_NAME="Tracker OSP Collection"; //$NON-NLS-1$
public static final String TRACKER_INFO_URL="http://physlets.org/tracker/library/comPADRE_collection.html"; //$NON-NLS-1$
public static final String PRIMARY_ONLY="&OSPPrimary=Subject"; //$NON-NLS-1$
public static final String GENERIC_COLLECTION_NAME="ComPADRE OSP Collection"; //$NON-NLS-1$
public static final String ABOUT_OSP="About OSP and ComPADRE"; //$NON-NLS-1$
public static final String HOST="www.compadre.org"; //$NON-NLS-1$
/**
* Loads a collection using a specified comPADRE search query.
*
* @param collection the LibraryCollection to load
* @param query the search query
* @return true if successfully loaded
*/
protected static boolean load(LibraryCollection collection, String query) {
try {
URL url = new URL(query);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc = factory.newDocumentBuilder().parse(url.openStream());
// writeXmlFile(doc, "compadre_catalog.txt"); // for testing
NodeList nodeList = doc.getElementsByTagName("Identify"); //$NON-NLS-1$
boolean success = false;
for (int i=0; i<nodeList.getLength(); i++) {
success = loadSubtrees(collection, nodeList.item(i).getChildNodes(),
"osp-subject", "") || success; //$NON-NLS-1$ //$NON-NLS-2$
}
return success;
}
catch(Exception e) {
e.printStackTrace();
}
return false;
}
/**
* Loads a collection with subtree collections that meet the specified requirements.
*
* @param collection the LibraryCollection to load
* @param nodeList a list of Nodes
* @param attributeType the desired attribute
* @param serviceParameter the desired service parameter
* @return true if at least one subtree collection was loaded
*/
protected static boolean loadSubtrees(LibraryCollection collection, NodeList nodeList,
String attributeType, String serviceParameter) {
boolean success = false;
String dblClick = ToolsRes.getString("LibraryComPADRE.Description.DoubleClick"); //$NON-NLS-1$
for (int i=0; i<nodeList.getLength(); i++) {
if (!(nodeList.item(i) instanceof Element)) continue;
Element node = (Element)nodeList.item(i);
if (node.getNodeName().equals("sub-tree-set") && attributeType.equals(node.getAttribute("type")) ) { //$NON-NLS-1$ //$NON-NLS-2$
List<Node> subTrees = getAllChildren(node, "sub-tree"); //$NON-NLS-1$
if (subTrees.size()>0) { // node has subcategories
String unclassifiedURL = null;
for (int j=0; j<subTrees.size(); j++) {
if (!(subTrees.get(j) instanceof Element)) continue;
Element subtree = (Element)subTrees.get(j);
String name = subtree.getAttribute("name"); //$NON-NLS-1$
String serviceParam = subtree.getAttribute("service-parameter"); //$NON-NLS-1$
serviceParam = serviceParameter+"&"+ResourceLoader.getNonURIPath(serviceParam); //$NON-NLS-1$
if (name.equals("Unclassified")) { // unclassified node is processed last and adds its records to the parent //$NON-NLS-1$
unclassifiedURL = serviceParam;
continue;
}
LibraryCollection subCollection = new LibraryCollection(name);
collection.addResource(subCollection);
success = true;
if (getAllChildren(subtree, "sub-tree-set").isEmpty()) { // has no subcategories //$NON-NLS-1$
String nodeName = "<h2>"+name+"</h2><blockquote>"; //$NON-NLS-1$ //$NON-NLS-2$
subCollection.setDescription(nodeName+dblClick+"</blockquote>"); //$NON-NLS-1$
subCollection.setTarget(serviceParam);
}
else
loadSubtrees(subCollection, subtree.getChildNodes(), attributeType+"-detail", serviceParam); //$NON-NLS-1$
}
if (unclassifiedURL!=null) {
collection.setTarget(unclassifiedURL);
}
}
}
}
return success;
}
/**
* Loads ComPADRE records into a LibraryTreeNode collection.
*
* @param treeNode the LibraryTreeNode to load--note record MUST be a collection
* @return true if one or more ComPADRE records were successfully loaded
*/
protected static boolean loadResources(LibraryTreeNode treeNode) {
LibraryCollection collection = (LibraryCollection)treeNode.record;
boolean success = false;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
String urlPath = treeNode.getAbsoluteTarget();
URL url = new URL(urlPath);
Document doc = factory.newDocumentBuilder().parse(url.openStream());
// writeXmlFile(doc, "compadre_resource.txt"); // for testing
// look at all ComPADRE records in the document
NodeList list = doc.getElementsByTagName("record"); //$NON-NLS-1$
for (int i=0; i<list.getLength(); i++) { // process nodes
Node node = list.item(i);
String ospType = getChildValue(node, "osp-type"); //$NON-NLS-1$
String[] attachment = null;
if (ospType.startsWith("EJS")) { //$NON-NLS-1$
attachment = getAttachment(node, "Source Code"); //$NON-NLS-1$
}
else {
attachment = getAttachment(node, "Main"); //$NON-NLS-1$
if (attachment==null) {
attachment = getAttachment(node, "Supplemental"); //$NON-NLS-1$
}
}
// ignore if there is no associated attachment
if (attachment==null) continue;
// create and add a new record to this node's collection
String name = getChildValue(node, "title"); //$NON-NLS-1$
LibraryResource record = new LibraryResource(name);
collection.addResource(record);
if (loadResource(record, node, attachment, treeNode)) {
success = true;
record.setProperty("reload_url", urlPath); //$NON-NLS-1$
}
}
}
catch(Exception e) { e.printStackTrace(); }
// clear the collection target and description
collection.setDescription(null);
collection.setTarget(null);
return success;
}
/**
* Reloads a ComPADRE record into a LibraryTreeNode.
*
* @param treeNode the LibraryTreeNode to reload
* @return true if successfully reloaded
*/
protected static boolean reloadResource(LibraryTreeNode treeNode, String urlPath) {
boolean success = false;
try {
LibraryResource record = treeNode.record;
URL url = new URL(urlPath);
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
Document doc = factory.newDocumentBuilder().parse(url.openStream());
// look through all ComPADRE records for a match
NodeList list = doc.getElementsByTagName("record"); //$NON-NLS-1$
for (int i=0; i<list.getLength(); i++) { // process nodes
Node node = list.item(i);
String ospType = getChildValue(node, "osp-type"); //$NON-NLS-1$
String[] attachment = null;
if (ospType.startsWith("EJS")) { //$NON-NLS-1$
attachment = getAttachment(node, "Source Code"); //$NON-NLS-1$
}
else {
attachment = getAttachment(node, "Main"); //$NON-NLS-1$
if (attachment==null) {
attachment = getAttachment(node, "Supplemental"); //$NON-NLS-1$
}
}
// ignore if there is no associated attachment
if (attachment==null) continue;
// check to see that the target is the one desired
String downloadURL = processURL(attachment[0]);
if (!downloadURL.equals(record.getTarget())) continue;
return loadResource(record, node, attachment, treeNode);
}
}
catch(Exception e) { e.printStackTrace(); }
return success;
}
/**
* Reloads a ComPADRE record into a LibraryTreeNode.
*
* @param treeNode the LibraryTreeNode to reload
* @return true if successfully reloaded
*/
protected static boolean loadResource(LibraryResource record, Node node, String[] attachment, LibraryTreeNode treeNode) {
try {
// get the node data and create the HTML code
String downloadURL = processURL(attachment[0]);
record.setTarget(downloadURL);
String name = getChildValue(node, "title"); //$NON-NLS-1$
record.setName(name);
record.setProperty("download_filename", attachment[1]); //$NON-NLS-1$
String type = getChildValue(node, "osp-type"); //$NON-NLS-1$
if (type.toUpperCase().startsWith("EJS")) { //$NON-NLS-1$
type = LibraryResource.EJS_TYPE;
record.setType(type);
}
else if (type.toUpperCase().startsWith("TRACKER")) { //$NON-NLS-1$
type = LibraryResource.TRACKER_TYPE;
record.setType(type);
}
else record.setType(LibraryResource.UNKNOWN_TYPE);
String description = getChildValue(node, "description"); //$NON-NLS-1$
String infoURL = getChildValue(node, "information-url"); //$NON-NLS-1$
String thumbnailURL = getChildValue(node,"thumbnail-url"); //$NON-NLS-1$
String authors = ""; //$NON-NLS-1$
for (Node next: getAllChildren(getFirstChild(node, "contributors"), "contributor")) { //$NON-NLS-1$ //$NON-NLS-2$
Element el = (Element)next;
if ("Author".equals(el.getAttribute("role"))) //$NON-NLS-1$ //$NON-NLS-2$
authors += getNodeValue(next)+", "; //$NON-NLS-1$
}
if (authors.endsWith(", ")) //$NON-NLS-1$
authors = authors.substring(0, authors.length()-2);
// cache the thumbnail
File cachedFile = ResourceLoader.getOSPCacheFile(thumbnailURL);
String cachePath = cachedFile.getAbsolutePath();
record.setThumbnail(cachePath);
if (!cachedFile.exists()) {
treeNode.new ThumbnailLoader(thumbnailURL, cachePath).execute();
}
thumbnailURL = ResourceLoader.getURIPath(cachePath);
// get the html code and set the description
String htmlCode = LibraryResource.getHTMLBody(name, type, thumbnailURL, description, authors, null, infoURL, attachment);
record.setDescription(htmlCode);
// convert <osp-subject> information into keywords and add metadata to the library record
record.setMetadata(null);
ArrayList<String> words = new ArrayList<String>();
for (Node next: getAllChildren(node, "osp-subject")) { //$NON-NLS-1$
String[] subjects = getNodeValue(next).split(" / "); //$NON-NLS-1$
for (String s: subjects) {
// skip "General" subject--too vague
if (s.equals("General")) continue; //$NON-NLS-1$
if (!words.contains(s)) words.add(s);
}
}
if (!words.isEmpty()) {
StringBuffer buf = new StringBuffer();
for (String s: words) {
buf.append(s+", "); //$NON-NLS-1$
}
String keywords = buf.toString();
keywords = keywords.substring(0, keywords.length()-2);
record.addMetadata(new LibraryResource.Metadata("keywords", keywords)); //$NON-NLS-1$
}
if (!"".equals(authors)) //$NON-NLS-1$
record.addMetadata(new LibraryResource.Metadata("author", authors)); //$NON-NLS-1$
return true;
}
catch(Exception e) { e.printStackTrace(); }
return false;
}
/**
* Returns data for a downloadable DOM Node attachment.
*
* @param node the DOM Node
* @param attachmentType the attachment type
* @return String[] {URL, filename, size in Bytes}, or null if no attachment found
*/
protected static String[] getAttachment(Node node, String attachmentType) {
String id = getChildValue(node, "file-identifier"); //$NON-NLS-1$
NodeList childList = node.getChildNodes();
String[] attachment = null;
for (int i=0,n=childList.getLength(); i<n; i++) {
Node child = childList.item(i);
if (!child.getNodeName().equals("attached-document")) continue; //$NON-NLS-1$
Node fileTypeNode = getFirstChild(child,"file-type"); //$NON-NLS-1$
if (fileTypeNode!=null && attachmentType.equals(getNodeValue(fileTypeNode))) {
Node urlNode = getFirstChild(child,"download-url"); //$NON-NLS-1$
if (urlNode!=null) { // found downloadable attachment
// keep first attachment or (preferred) attachment with the same id as the node
if (attachment==null || id.equals(getChildValue(child, "file-identifier"))) { //$NON-NLS-1$
String attachmentURL = getNodeValue(urlNode);
Element fileNode = (Element)getFirstChild(child,"file-name"); //$NON-NLS-1$
if (fileNode!=null) {
attachment = new String[] {attachmentURL, getNodeValue(fileNode),
fileNode.getAttribute("file-size")}; //$NON-NLS-1$
}
else attachment = new String[] {attachmentURL, null, null};
}
}
}
}
return attachment;
}
/**
* Returns the first child node with the given name.
*
* @param parent the parent Node
* @param name the child name
* @return the first child Node found, or null if none
*/
protected static Node getFirstChild(Node parent, String name) {
NodeList childList = parent.getChildNodes();
for (int i=0,n=childList.getLength(); i<n; i++) {
Node child = childList.item(i);
if (child.getNodeName().equals(name))
return child;
}
return null;
}
/**
* Returns all child nodes with the given name.
*
* @param parent the parent Node
* @param name the name
* @return a list of Nodes (may be empty)
*/
protected static List<Node> getAllChildren(Node parent, String name) {
java.util.List<Node> list = new ArrayList<Node>();
NodeList childrenList = parent.getChildNodes();
for (int i=0,n=childrenList.getLength(); i<n; i++) {
Node child = childrenList.item(i);
if (child.getNodeName().equals(name)) list.add(child);
}
return list;
}
/**
* Gets the value of a Node.
*
* @param node the Node
* @return the value
*/
protected static String getNodeValue(Node node) {
for (Node child = node.getFirstChild(); child!=null; child=child.getNextSibling() ){
if (child.getNodeType()==Node.TEXT_NODE)
return child.getNodeValue();
}
return null;
}
/**
* Gets the value of the first child node with a given name.
* @param parent the parent Node
* @param name the name of the child
* @return the value of the first child found, or null if none
*/
protected static String getChildValue(Node parent, String name) {
Node node = getFirstChild(parent,name);
if (node!=null)
return getNodeValue(node);
return null;
}
/**
* Replaces "&" with "&" in HTML code.
* @param url the HTML code
* @return the clean URL string
*/
protected static String processURL(String url) {
StringBuffer processed=new StringBuffer();
int index = url.indexOf("&"); //$NON-NLS-1$
while (index>=0) {
processed.append(url.subSequence(0,index+1));
url = url.substring(index+5);
index = url.indexOf("&"); //$NON-NLS-1$
}
processed.append(url);
return processed.toString();
}
/**
* Writes a DOM document to a file for testing.
*
* @param doc the Document
* @param filename the filename to write to
* @return the String contents of the document
*/
protected static String writeXmlFile(Document doc, String filename) {
try {
// Prepare the DOM document for writing
Source source = new DOMSource(doc);
// Prepare the output file
File file = new File(filename);
Result result = new StreamResult(file);
// Write the DOM document to the file
Transformer xformer = TransformerFactory.newInstance().newTransformer();
xformer.transform(source, result);
return ResourceLoader.getString(filename);
} catch (Exception e) {}
return null;
}
/**
* Returns a descriptive name for a given ComPADRE path (query).
*
* @param path the query string
* @return the name of the collection
*/
public static String getCollectionName(String path) {
if (path.startsWith(EJS_SERVER_TREE))
return EJS_COLLECTION_NAME;
if (path.startsWith(TRACKER_SERVER_TREE))
return TRACKER_COLLECTION_NAME;
return GENERIC_COLLECTION_NAME;
}
/**
* Returns the LibraryCollection for a given ComPADRE path (query).
*
* @param path the query string
* @return the collection
*/
protected static LibraryCollection getCollection(String path) {
String name = getCollectionName(path);
boolean primarySubjectOnly = path.indexOf(PRIMARY_ONLY)>-1;
LibraryCollection collection = new LibraryCollection(name);
if (name.equals(EJS_COLLECTION_NAME)) {
collection.setHTMLPath(EJS_INFO_URL);
}
else if (name.equals(TRACKER_COLLECTION_NAME)) {
collection.setHTMLPath(TRACKER_INFO_URL);
}
LibraryResource aboutOSP = new LibraryResource(ABOUT_OSP);
aboutOSP.setHTMLPath(OSP_INFO_URL);
collection.addResource(aboutOSP);
load(collection, path);
String base = EJS_SERVER_RECORDS;
if (name.equals(TRACKER_COLLECTION_NAME)) {
base = TRACKER_SERVER_RECORDS;
}
if (primarySubjectOnly) base += PRIMARY_ONLY;
collection.setBasePath(base);
return collection;
}
/**
* Returns the collection path for an EJS or tracker tree.
*
* @param path the ComPADRE query string
* @param primarySubjectOnly true to limit results to their primary subject
* @return the corrected ComPADRE query string
*/
protected static String getCollectionPath(String path, boolean primarySubjectOnly) {
boolean isPrimary = path.endsWith(PRIMARY_ONLY);
if (isPrimary && primarySubjectOnly) return path;
if (!isPrimary && !primarySubjectOnly) return path;
if (!isPrimary && primarySubjectOnly) return path+PRIMARY_ONLY;
return path.substring(0, path.length()-PRIMARY_ONLY.length());
}
/**
* Determines if a path is a valid ComPADRE query.
*
* @param path the path
* @return true if path is a valid ComPADRE query string
*/
protected static boolean isComPADREPath(String path) {
if (path.startsWith(EJS_SERVER_TREE)
|| path.startsWith(TRACKER_SERVER_TREE))
return true;
return false;
}
/**
* Determines if a query path limits results to the primary subject only.
*
* @param path the path
* @return true if path contains a primary-subject-only flag
*/
protected static boolean isPrimarySubjectOnly(String path) {
return path.indexOf(PRIMARY_ONLY)>-1;
}
}