package com.rectang.xsm.io; import java.io.*; import java.util.*; import org.jdom.*; import org.jdom.input.SAXBuilder; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; import com.rectang.xsm.XSM; import com.rectang.xsm.site.Site; public class RemoteDocument extends File { protected static Hashtable documents = new Hashtable(); private static EvictionThread evict = null; protected static SAXBuilder builder = new SAXBuilder(); protected Element root; protected Document dom; public static RemoteDocument getDoc( String file ) { if ( documents.containsKey( file ) ) { TimedDocument ret = (TimedDocument) documents.get( file ); if ( ret.getModifiedTime() >= ret.getDoc().getModifiedTime() ) { return ret.getDoc(); } documents.remove( file ); } return new RemoteDocument( file ); } public static RemoteDocument getDoc( Site site, String file ) { return getDoc( calculateFileName( site, file, true ) ); } public static RemoteDocument getDoc( Site site, String file, boolean makeXML ) { return getDoc( calculateFileName( site, file, makeXML ) ); } private RemoteDocument( String file ) { super( file ); if ( evict == null ) { evict = new EvictionThread(); evict.start(); } try { load( new FileInputStream( file ) ); } catch ( Exception e ) { dom = new Document(); root = null; } documents.put( file, new TimedDocument( this ) ); } public static String calculateFileName( Site site, String file, boolean makeXML ) { String fileName = file; if ( fileName.charAt( 0 ) != File.separatorChar ) { fileName = File.separatorChar + fileName; } String theFile = XSM.getConfig().getDataDir() + site.getId() + fileName; if ( makeXML && !theFile.endsWith( ".xml" ) ) { theFile += ".xml"; } return theFile; } public void load( InputStream is ) { try { synchronized (this) { dom = builder.build( is ); } root = dom.getRootElement(); } catch ( Exception e ) { dom = new Document(); root = null; } finally { try { is.close(); } catch ( IOException e ) { /* nothing we can do */ } } } public Element getRootElement() { return root; } public void setRootElement( Element root ) { this.root = root; dom.setRootElement( root ); } public static Element addContentAfterElement( Element parent, int at, Content child ) { int id = 0, ptr = 0; List kids = parent.getContent(); Iterator eachKid = kids.iterator(); while ( eachKid.hasNext() && ptr < at ) { Content node = (Content) eachKid.next(); if ( node instanceof Element ) { ptr++; } id++; } return parent.addContent( id, child ); } public boolean save() { try { OutputStream out; out = new FileOutputStream( this ); (new XMLOutputter( Format.getPrettyFormat() )).output( dom, out ); out.close(); documents.remove( getPath() ); return true; } catch ( Exception e ) { e.printStackTrace(); return false; } } public InputStream getInputStream() throws IOException { return new FileInputStream( this ); } public OutputStream getOutputStream() throws IOException { return new FileOutputStream( this ); } public String toString() { return "Remote document: " + getPath(); } public boolean mkparentdirs() { if ( getParentFile().exists() ) { return true; } return getParentFile().mkdirs(); } public boolean delete() { boolean ret = true; if ( isDirectory() ) { ret = delete( listFiles() ); } documents.remove( getPath() ); return ret && super.delete(); } private static boolean delete( File[] files ) { boolean ret = true; for ( int i = 0; i < files.length; i++ ) { if ( files[i].isDirectory() ) { ret = ret && delete( files[i].listFiles() ); } ret = ret && files[i].delete(); documents.remove( files[i].getPath() ); } return ret; } public boolean rename( Site site, String newName, boolean makeXML ) { return renameTo( new File( calculateFileName( site, newName, makeXML ) ) ); } public long getModifiedTime() { return lastModified(); } class TimedDocument { private long stamp; // when we last accesed this document private RemoteDocument doc; // the document private long mtime; // last time the document was modified public TimedDocument( RemoteDocument doc ) { this.doc = doc; this.stamp = System.currentTimeMillis(); // stamp it with the current time this.mtime = doc.getModifiedTime(); } public RemoteDocument getDoc() { this.stamp = System.currentTimeMillis(); return doc; } public long getTimestamp() { return stamp; } public long getModifiedTime() { return mtime; } } static class EvictionThread extends Thread { public static final long EVICT_TIMEOUT = 60 * 1000; public static final long EVICT_SLEEP = 5 * 60 * 1000; public void run() { while ( true ) { try { Thread.sleep( EVICT_SLEEP ); } catch ( InterruptedException timeout ) { /* run the evacuation */ } long expiry = System.currentTimeMillis() - EVICT_TIMEOUT; Iterator docs = documents.keySet().iterator(); while ( docs.hasNext() ) { String nextKey = (String) docs.next(); TimedDocument next = (TimedDocument) documents.get( nextKey ); if ( next.getTimestamp() < expiry ) { docs.remove(); } } } } } }