/* * Bookmark.java * * Created on November 9, 2007, 10:32 AM * * To change this template, choose Tools | Template Manager * and open the template in the editor. */ package org.autoplot.bookmarks; import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.GZIPInputStream; import javax.imageio.ImageIO; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathFactory; import org.das2.util.Base64; import org.das2.util.monitor.NullProgressMonitor; import org.autoplot.datasource.DataSetURI; import org.autoplot.datasource.DataSourceUtil; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.Text; import org.w3c.dom.ls.DOMImplementationLS; import org.w3c.dom.ls.LSOutput; import org.w3c.dom.ls.LSSerializer; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * Internal representation of a Bookmark, containing an Autoplot URI, title, and additional documentation * for the URI. This can also be a folder that contains a list of Bookmarks, or a remote folder that is controlled be * a remote bookmarks file./ * @author jbf */ public abstract class Bookmark { public static final String TITLE_ERROR_OCCURRED = "Error Occurred"; public static final String MSG_NO_REMOTE= "[remote*]"; public static final String TOOLTIP_NO_REMOTE = "Using cached version because
remote folder based on contents of remote URL
%{URL}
which is not available. "; public static final String MSG_REMOTE= ""; public static final String TOOLTIP_REMOTE = "Bookmark folder based on contents of remote URL
%{URL}"; public static final String MSG_NOT_LOADED= "[loading...]"; public static final String TOOLTIP_NOT_LOADED = "Checking to see if remote bookmark list has changed.
%{URL}"; /** * limit on the number of remote containing remote containing remote... */ public static final int REMOTE_BOOKMARK_DEPTH_LIMIT = 5; private static final Logger logger= org.das2.util.LoggerManager.getLogger("autoplot.bookmarks"); public static List parseBookmarks(String data) throws IOException, SAXException, BookmarksException { return parseBookmarks(data,0); } public static List parseBookmarks(String data,int depth) throws IOException, SAXException, BookmarksException { try { Reader in = new BufferedReader(new StringReader(data)); DocumentBuilder builder; builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); InputSource source = new InputSource(in); Document document = builder.parse(source); List books= parseBookmarks(document.getDocumentElement(),depth); return books; } catch (ParserConfigurationException ex) { throw new RuntimeException(ex); } } /** * read in the bookmarks file, which should be an xml file with the top node <bookmark-list>. * @param url local or remote file. * @return * @throws SAXException * @throws IOException * @throws org.autoplot.bookmarks.BookmarksException */ public static List parseBookmarks( URL url ) throws IOException, SAXException, BookmarksException { try { File file= DataSetURI.downloadResourceAsTempFile( url, new NullProgressMonitor() ); Reader in = new BufferedReader( new FileReader(file) ); DocumentBuilder builder; builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); InputSource source = new InputSource(in); Document document = builder.parse(source); return parseBookmarks(document.getDocumentElement()); } catch (ParserConfigurationException ex) { throw new RuntimeException(ex); } } /** * * @param data * @return * @throws IOException * @throws SAXException * @throws BookmarksException */ public static Bookmark parseBookmark(String data) throws IOException, SAXException, BookmarksException { try { Reader in = new BufferedReader(new StringReader(data)); DocumentBuilder builder; builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); InputSource source = new InputSource(in); Document document = builder.parse(source); String vers= document.getDocumentElement().getAttribute("version"); return parseBookmark( document.getDocumentElement(),vers,1 ); } catch (ParserConfigurationException ex) { throw new RuntimeException(ex); } } /** * check to see if there are remote nodes that still need to be resolved * @param contents * @return */ private static boolean checkForUnresolved( List contents ) { for ( Bookmark book: contents ) { if ( book instanceof Bookmark.Folder ) { Bookmark.Folder bf= (Bookmark.Folder) book; if ( bf.remoteStatus==Bookmark.Folder.REMOTE_STATUS_NOT_LOADED ) return true; if ( checkForUnresolved( bf.getBookmarks() ) ) { //TODO: no check for cycles return true; } } } return false; } /** * read in a remote bookmarks file. * @param remoteUrl the location of the file. * @param remoteLevel limit the depth of remote bookmarks. For example, remoteLevel=1 indicates this should be read but no remote bookmarks ought to be read. * @param startAtRoot if true, then include the root nodes, otherwise return the contents of the folders. * @param contents list where the bookmarks should be stored. * @return true if there is a remote bookmark within the file. * @throws MalformedRemoteBookmarksException if the remote file contains more that one remote bookmark folder. */ protected static RemoteStatus getRemoteBookmarks( String remoteUrl, int remoteLevel, boolean startAtRoot, List contents ) throws MalformedRemoteBookmarksException { InputStream in=null; boolean remoteRemote= false; boolean offline= false; RemoteStatus result= new RemoteStatus(); logger.log(Level.FINE, "getRemoteBookmarks({0},...)", remoteUrl); logger.log(Level.FINEST, " remoteLevel={0} startAtRoot={1}", new Object[] { remoteLevel, startAtRoot } ); try { URL rurl= new URL(remoteUrl); NodeList nl; // Copy remote file to local string, so we can check content type. Autoplot.org always returns 200 okay, even if file doesn't exist. // See if the URI is file-like, not containing query parameters, in which case we allow caching to occur. logger.log(Level.FINE, "Using downloadResourceAsTempFile route. Files are cached for up to 3600 seconds" ); in = new FileInputStream( DataSetURI.downloadResourceAsTempFile( rurl, 3600, new NullProgressMonitor()) ); if ( remoteUrl.endsWith(".gz" ) ) { logger.fine("bookmarks stream is uncompressed because of .gz"); in= new GZIPInputStream( in ); } logger.fine(" got it..."); ByteArrayOutputStream boas=new ByteArrayOutputStream(); WritableByteChannel dest = Channels.newChannel(boas); ReadableByteChannel src = Channels.newChannel(in); DataSourceUtil.transfer(src, dest); in.close(); in= null; // don't close it again. String sin= new String( boas.toByteArray() ); if ( !sin.startsWith("1 ) { throw new MalformedRemoteBookmarksException("remote bookmarks file can only contain one root bookmark folder: "+remoteUrl); } Element flist = (Element) nl.item(0); // the bookmark list. if ( flist==null ) { // The remote folder itself can contain remote folders, //String remoteUrl2= (String)xpath.evaluate( "/bookmark-list/bookmark-folder/@remoteUrl", document, XPathConstants.STRING ); String remoteUrl2= (String)xpath.evaluate( "//bookmark-list/bookmark-folder/@remoteUrl", document, XPathConstants.STRING ); if ( remoteUrl2.length()>0 ) { logger.log(Level.FINE, "another remote folder: {0} at {1}", new Object[]{remoteUrl2, remoteLevel}); remoteRemote= true; // avoid warning } } else { String remoteUrl2= (String)xpath.evaluate( "//bookmark-list/bookmark-folder/@remoteUrl", document, XPathConstants.STRING ); //TODO: verify that we can have remote in lower position. if ( remoteUrl2.length()>0 ) { logger.log(Level.FINE, "another remote folder: {0} at {1}", new Object[]{remoteUrl2, remoteLevel}); remoteRemote= true; // avoid warning } String vers1= (String) xpath.evaluate("/bookmark-list/@version", document, XPathConstants.STRING ); List contents1 = parseBookmarks( flist, vers1, remoteLevel-1 ); remoteRemote= checkForUnresolved(contents1); contents.addAll(contents1); } } catch ( MalformedRemoteBookmarksException ex ) { throw ex; } catch (Exception ex) { ex.printStackTrace(); Bookmark.Item err= new Bookmark.Item(""); err.description= ex.toString(); err.setTitle(TITLE_ERROR_OCCURRED); // note TITLE_ERROR_OCCURRED is used to detect this bookmark. try { err.setIcon( new ImageIcon( ImageIO.read( Bookmark.class.getResource( "/org/autoplot/resources/warning-icon.png" ) ) ) ); } catch (IOException ex2) { logger.log( Level.SEVERE, ex2.getMessage(), ex2 ); } result.statusMsg= ex.toString(); contents.add( err ); offline= true; } finally { if ( in!=null ) { try { in.close(); } catch ( IOException ex ) { logger.log( Level.SEVERE, ex.getMessage(), ex ); } } } //result.remoteURL= remoteUrl; // for debugging. //result.depth= remoteLevel; result.remoteRemote= remoteRemote; result.status= offline ? Bookmark.Folder.REMOTE_STATUS_UNSUCCESSFUL : Bookmark.Folder.REMOTE_STATUS_SUCCESSFUL; logger.log( Level.FINE, "{0}", result); return result; } /** * xml utility for getting the first child node with the given name. * @param element * @param name * @return null or the first child name with the given name. */ private static Node getChildElement( Node element, String name ) { NodeList nl= element.getChildNodes(); for ( int i=0; i