/*
* Copyright (c) 2007-2012 The Broad Institute, Inc.
* SOFTWARE COPYRIGHT NOTICE
* This software and its documentation are the copyright of the Broad Institute, Inc. All rights are reserved.
*
* This software is supplied without any warranty or guaranteed support whatsoever. The Broad Institute is not responsible for its use, misuse, or functionality.
*
* This software is licensed under the terms of the GNU Lesser General Public License (LGPL),
* Version 2.1 which is available at http://www.opensource.org/licenses/lgpl-2.1.php.
*/
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.broad.igv.ui.action;
//~--- non-JDK imports --------------------------------------------------------
import org.apache.log4j.Logger;
import org.broad.igv.PreferenceManager;
import org.broad.igv.feature.genome.GenomeManager;
import org.broad.igv.ui.IGV;
import org.broad.igv.ui.ResourceTree;
import org.broad.igv.ui.util.MessageUtils;
import org.broad.igv.util.ParsingUtils;
import org.broad.igv.util.ResourceLocator;
import org.broad.igv.util.Utilities;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.awt.event.ActionEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
/**
* @author jrobinso
*/
public class LoadFromServerAction extends MenuAction {
static Logger log = Logger.getLogger(LoadFromServerAction.class);
IGV mainFrame;
// Keep track of authorization failures so user isn't constantly harranged
static HashSet<String> failedURLs = new HashSet();
public LoadFromServerAction(String label, int mnemonic, IGV mainFrame) {
super(label, null, mnemonic);
this.mainFrame = mainFrame;
}
public static String getGenomeDataURL(String genomeId) {
String urlString = PreferenceManager.getInstance().getDataServerURL();
String genomeURL = urlString.replaceAll("\\$\\$", genomeId);
return genomeURL;
}
@Override
public void actionPerformed(ActionEvent evt) {
mainFrame.setStatusBarMessage("Loading ...");
String genomeId = GenomeManager.getInstance().getGenomeId();
String genomeURL = getGenomeDataURL(genomeId);
try {
LinkedHashSet<String> nodeURLs = getNodeURLs(genomeURL);
if (nodeURLs == null || nodeURLs.isEmpty()) {
MessageUtils.showMessage("No datasets are available for the current genome (" + genomeId + ").");
} else {
List<ResourceLocator> locators = loadNodes(nodeURLs);
if (locators != null) {
mainFrame.loadTracks(locators);
}
}
} finally {
mainFrame.showLoadedTrackCount();
}
}
public static LinkedHashSet<String> getNodeURLs(String genomeURL) {
InputStream is = null;
LinkedHashSet<String> nodeURLs = null;
try {
is = ParsingUtils.openInputStreamGZ(new ResourceLocator(genomeURL));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
nodeURLs = getResourceUrls(bufferedReader);
} catch (IOException e) {
//This is pretty common, if there is no data registry file for the genome the file won't exist
log.error("Error loading genome registry file", e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
log.error("Error closing input stream", e);
}
}
}
return nodeURLs;
}
private List<ResourceLocator> loadNodes(final LinkedHashSet<String> xmlUrls) {
if ((xmlUrls == null) || xmlUrls.isEmpty()) {
log.error("No datasets are available from this server for the current genome (");
return null;
}
try {
Document masterDocument = createMasterDocument(xmlUrls);
/**
* Resource Tree
*/
LinkedHashSet<ResourceLocator> selectedLocators =
ResourceTree.showResourceTreeDialog(mainFrame.getMainFrame(),
masterDocument, "Available Datasets");
List<ResourceLocator> newLoadList = new ArrayList();
if (selectedLocators != null) {
for (ResourceLocator locator : selectedLocators) {
// Don't reload data that is already loaded
if (IGV.getInstance().getDataResourceLocators().contains(locator)) {
continue;
}
newLoadList.add(locator);
}
}
return newLoadList;
} catch (Exception e) {
log.error("Could not load information from server", e);
return null;
}
}
public static Document createMasterDocument(Collection<String> xmlUrls) throws ParserConfigurationException {
StringBuffer buffer = new StringBuffer();
Document masterDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
Element rootNode = masterDocument.createElement("Global");
rootNode.setAttribute("name", "Available Datasets");
rootNode.setAttribute("version", "1");
masterDocument.appendChild(rootNode);
// Merge all documents into one xml document for processing
for (String url : xmlUrls) {
// Skip urls that have previously failed due to authorization
if (failedURLs.contains(url)) {
continue;
}
try {
Document xmlDocument = readXMLDocument(url, buffer);
if (xmlDocument != null) {
Element global = xmlDocument.getDocumentElement();
masterDocument.getDocumentElement().appendChild(masterDocument.importNode(global, true));
}
} catch (Exception e) {
String message = "Cannot create an XML Document from " + url.toString();
log.error(message, e);
continue;
}
}
if (buffer.length() > 0) {
String message = "<html>The following urls could not be processed due to load failures:<br>" + buffer.toString();
MessageUtils.showMessage(message);
}
return masterDocument;
}
private static Document readXMLDocument(String url, StringBuffer errors) {
InputStream is = null;
Document xmlDocument = null;
try {
is = ParsingUtils.openInputStreamGZ(new ResourceLocator(url));
xmlDocument = Utilities.createDOMDocumentFromXmlStream(is);
xmlDocument = resolveIncludes(xmlDocument, errors);
} catch (SAXException e) {
log.error("Invalid XML resource: " + url, e);
errors.append(url + "<br><i>" + e.getMessage());
} catch (java.net.SocketTimeoutException e) {
log.error("Connection time out", e);
errors.append(url + "<br><i>Connection time out");
} catch (IOException e) {
log.error("Error accessing " + url.toString(), e);
errors.append(url + "<br><i>" + e.getMessage());
} catch (ParserConfigurationException e) {
log.error("Parser configuration error for:" + url, e);
errors.append(url + "<br><i>" + e.getMessage());
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
log.error("Error closing stream for: " + url, e);
}
}
}
return xmlDocument;
}
private static Document resolveIncludes(Document document, StringBuffer errors) {
NodeList includeNodes = document.getElementsByTagName("Include");
if (includeNodes.getLength() == 0) {
return document;
}
int size = includeNodes.getLength();
// Copy the nodes as we'll be modifying the tree. This is neccessary!
Node[] tmp = new Node[size];
for (int i = 0; i < size; i++) {
tmp[i] = includeNodes.item(i);
}
for (Node item : tmp) {
NamedNodeMap nodeMap = item.getAttributes();
if (nodeMap == null) {
log.info("XML node " + item.getNodeName() + " has no attributes");
} else {
Attr path = (Attr) item.getAttributes().getNamedItem("path");
if (path == null) {
log.info("XML node " + item.getNodeName() + " is missing a path attribute");
} else {
Node parent = item.getParentNode();
//log.info("Loading node " + path.getValue());
Document doc = readXMLDocument(path.getValue(), errors);
if (doc != null) {
Element global = doc.getDocumentElement();
Node expandedNode = parent.getOwnerDocument().importNode(global, true);
parent.replaceChild(expandedNode, item);
}
}
}
}
return document;
}
/**
* Returns the complete list of URLs from the master registry file.
*
* @param bufferedReader
* @return
* @throws java.net.MalformedURLException
* @throws java.io.IOException
*/
private static LinkedHashSet<String> getResourceUrls(BufferedReader bufferedReader)
throws IOException {
LinkedHashSet<String> xmlFileUrls = new LinkedHashSet();
String xmlFileUrl;
while ((xmlFileUrl = bufferedReader.readLine()) != null) {
xmlFileUrl = xmlFileUrl.trim();
if(xmlFileUrl.length() == 0 || xmlFileUrl.startsWith("#")){
continue;
}
xmlFileUrls.add(xmlFileUrl);
}
return xmlFileUrls;
}
}