package com.rectang.xsm.io; import java.io.*; import java.lang.reflect.Constructor; import java.util.*; import org.jdom.*; import com.rectang.xsm.*; import com.rectang.xsm.types.*; import com.rectang.xsm.doc.DocElement; import com.rectang.xsm.doc.Type; import com.rectang.xsm.site.Site; import com.rectang.xsm.site.DocumentPage; import com.rectang.xsm.site.Page; import com.rectang.xsm.util.*; import org.apache.wicket.Component; import org.apache.velocity.VelocityContext; import org.headsupdev.support.java.IOUtil; public class XSMDocument implements Serializable { private RemoteDocument doc; private long LOCK_TIMEOUT = 1000 * 60 * 60 * 2; // 2 hour timeout on locks private MetaData metadata; private DocumentPage page; public static XSMDocument getXSMDoc( Site site, DocumentPage page ) { try { return getXSMDoc( site, page, false ); } catch ( RuntimeException e ) { System.err.println( "Unable to read document for page " + page.getPublishedPath() + ": " + e.getMessage() ); throw e; } } public static XSMDocument getXSMDoc( Site site, DocumentPage page, boolean create ) { return new XSMDocument( site, page, create ); } private XSMDocument( Site site, DocumentPage page, boolean create ) { doc = RemoteDocument.getDoc( makeFileName( page ) ); this.page = page; if ( doc.root == null ) { if ( create ) { doc.mkparentdirs(); try { OutputStreamWriter out = new OutputStreamWriter( doc.getOutputStream() ); out.write( "<xsmdoc><metadata></metadata><data></data></xsmdoc>" ); out.close(); doc.dom = RemoteDocument.builder.build( doc.getInputStream() ); } catch ( Exception e ) { e.printStackTrace(); } } } doc.root = doc.dom.getRootElement(); if ( getMetadataElement() != null ) { metadata = new MetaData( this ); } } public static String makeFileName( Page page ) { if ( page == null ) { return null; } String fileName = page.getPath(); if ( fileName.charAt( 0 ) != File.separatorChar ) { fileName = File.separatorChar + fileName; } return XSM.getConfig().getSiteDataDir( page.getSite() ) + fileName + ".xml"; } public DocumentPage getPage() { return page; } public Element getContentElement() { try { return (Element) doc.root.getChild( "data" ).getChildren().get( 0 ); } catch ( NullPointerException e ) { return null; } } public Element getRootElement() { return doc.getRootElement(); } public String getPath() { return doc.getPath(); } public boolean exists() { return doc.exists(); } /** * Just a handy method for system code to save without checking the user permissions * @return whether or not the save was successfull */ public boolean save() { return save( null ); } public boolean delete() { return doc.delete(); } public Element getMetadataElement() { return (doc.root == null) ? null : doc.root.getChild( "metadata" ); } public MetaData getMetadata() { return metadata; } public void setContentElement( Element content ) { if ( doc.root == null ) { doc.dom.setRootElement( new Element( "xsmdoc" ) ); doc.root = doc.dom.getRootElement(); } Element data = doc.root.getChild( "data" ); if ( data == null ) { doc.root.addContent( new Element( "data" ) ); data = doc.root.getChild( "data" ); } data.removeContent(); data.addContent( content ); } public boolean save( UserData user ) { return save( user, false ); } public boolean save( UserData user, boolean force ) { if ( user != null && (!force && !canEdit( user )) ) { return false; } metadata.save(); return doc.save(); } public String getLocked() { long modified = doc.getModifiedTime(); if ( modified >= System.currentTimeMillis() - LOCK_TIMEOUT ) { return doc.root.getAttributeValue( "lock" ); } /* lock has timed out */ return null; } public boolean isLocked() { String lock = getLocked(); return (lock != null && !lock.equals( "" )); } public boolean lockedBy( String user ) { String lock = getLocked(); return (lock != null && lock.equals( user )); } public boolean lockedByMe( UserData user ) { return lockedBy( user.getUsername() ); } public boolean lock( UserData user ) { if ( !canEdit( user ) ) { return false; } if ( isLocked() ) { return lockedByMe( user ); } doc.root.setAttribute( "lock", user.getUsername() ); return save( user ); } public boolean unlock( UserData user ) { return unlock( user, false ); } public boolean unlockForced( UserData user ) { return unlock( user, true ); } private boolean unlock( UserData user, boolean force ) { if ( !canEdit( user ) ) { return false; } if ( !isLocked() ) { return true; } if ( force ) { if ( !user.isSiteAdmin() && !isOwner( user ) ) { return false; } } else { if ( !lockedByMe( user ) ) { return false; } } String att = doc.root.getAttributeValue( "lock" ); if ( att == null || att.equals( "" ) ) { return true; } doc.root.removeAttribute( "lock" ); return save( user ); } public String getOwner() { String owner = doc.root.getAttributeValue( "owner" ); if ( owner == null ) { return ""; } return owner; } public boolean isOwner( String username ) { return getOwner().equals( username ); } public boolean isOwner( UserData user ) { return getOwner().equals( user.getUsername() ); } public boolean setOwner( UserData user, String owner ) { if ( !isOwner( user.getUsername() ) ) { return false; } doc.root.setAttribute( "owner", owner ); return save( user ); } public boolean canEdit( UserData user ) { return isOwner( user.getUsername() ) || getEditors().contains( user.getUsername() ) || user.isSiteEditor() || user.isSiteAdmin() || user.isXSMAdmin(); } public List /* String */ getEditors() { return StringUtils.stringToList( doc.root.getAttributeValue( "editors" ) ); } public boolean setEditors( List /* String */ editors, UserData user ) { doc.root.setAttribute( "editors", StringUtils.listToString( editors ) ); return save( user, true ); } public List /* String */ getWatchers() { return StringUtils.stringToList( doc.root.getAttributeValue( "watchers" ) ); } public boolean setWatchers( List /* String */ watchers, UserData user ) { doc.root.setAttribute( "watchers", StringUtils.listToString( watchers ) ); return save( user, true ); } public boolean publish( UserData user ) { String content; RootPair roots = getRootsAtIndex( "", user ); StringBuffer s = new StringBuffer(); roots.getType().publish( roots.getData(), s ); content = s.toString(); String fileName = "index.html"; if ( getType( user ) instanceof PHPFile ) { fileName = "index.php"; } PublishedFile pubFile = user.getSite().getPublishedDoc( page.getPublishedPath() + File.separatorChar + fileName ); return publishContent( pubFile, content, user ); } public boolean publishContent( PublishedFile pubFile, String content, UserData user ) { Writer writer = null; try { /* create a context and add data */ VelocityContext context = new VelocityContext(); Map<String, Object> contextItems = Engine.getContext( this, page, getType( user ), pubFile, user.getSite(), content, user ); for ( String key : contextItems.keySet() ) { context.put( key, contextItems.get( key ) ); } pubFile.mkparentdirs(); writer = new OutputStreamWriter( pubFile.getOutputStream() ); Engine.process( user.getSite(), context, writer ); IOUtil.close( writer ); /* only publish to index.html in the base of the site if we are the default file AND we are currently * writing this pages "index file" */ if ( user.getSite().getDefault().equals( getPage().getPath() ) ) { if ( pubFile.getFileName().equals( "index.html" ) || pubFile.getFileName().equals( "index.php" ) ) { pubFile = user.getSite().getPublishedDoc( pubFile.getFileName() ); try { writer = new OutputStreamWriter( pubFile.getOutputStream() ); Engine.process( user.getSite(), context, writer ); } catch ( Exception e ) { e.printStackTrace(); return false; } finally { IOUtil.close( writer ); } } } } catch ( Exception e ) { System.err.println( "Unable to publish page " + getPage().getPublishedPath() + ": " + e.getMessage() ); return false; } finally { IOUtil.close( writer ); } return true; } public boolean rename( Site site, DocumentPage newPage ) { File newLoc = new File( makeFileName( newPage ) ); newLoc.getParentFile().mkdir(); // we may be the first child page boolean ret = doc.renameTo( newLoc ); if ( ret ) { page = newPage; } return ret; } public String view( String index, UserData user ) { RootPair roots = getRootsAtIndex( index, user ); StringBuffer s = new StringBuffer(); roots.getType().view( roots.getData(), s ); return s.toString(); } public Component edit( String wicketId, String index, UserData user ) { RootPair roots = getRootsAtIndex( index, user ); return roots.getType().edit( wicketId, roots.getData(), index ); } // TODO figure if we missing create - was it ever called directly? public void add( String addNode, String index, UserData user ) throws Exception { RootPair roots = getRootsAtIndex( index, user ); DocElement typeRoot = roots.getType(); Element root = roots.getData(); if ( addNode == null ) { throw new Exception( "No parameter addnode specified" ); } String[] datum = addNode.split( "@" ); /* if no insert part we want to create a file */ if ( !datum[0].equals( "" ) ) { boolean isList = typeRoot instanceof com.rectang.xsm.doc.DocGroup; int auto_inc = 1; try { auto_inc = Integer.parseInt( root.getAttributeValue( "next_index" ) ); } catch ( NumberFormatException e ) { /* auto_inc is 1 */ } Element insert = new Element( datum[0] ); if ( isList ) { insert.setAttribute( "index", "" + auto_inc++ ); root.setAttribute( "next_index", "" + auto_inc ); } int num = 0; if ( datum.length > 1 ) { num = Integer.parseInt( datum[1] ); } RemoteDocument.addContentAfterElement( root, num, insert ); root = insert; /* we do not want this to execute if we are recursing */ if ( !typeRoot.getName().equals( datum[0] ) ) { typeRoot = typeRoot.getElement( datum[0] ); } } typeRoot.create( root ); } private RootPair getRootsAtIndex( String index, UserData user ) { Element root = getContentElement(); DocElement typeRoot = loadType( root.getName(), page, user ); if ( index == null || index.equals( "" ) ) { index = "/"; } if ( !index.equals( "/" ) ) { String[] elements = index.split( "/" ); for ( int x = 0; x < elements.length; x++ ) { String element = elements[x]; if ( element.equals( "" ) ) { continue; } String[] split = element.split( "@" ); int count = 0; try { count = Integer.parseInt( split[1] ); } catch ( NumberFormatException nfe ) {/* no problem */ } List kids = root.getChildren(/*name*/ ); root = (Element) kids.get( count ); /* we do not want this to execute if we are recursing */ if ( !typeRoot.getName().equals( root.getName() ) ) { typeRoot = typeRoot.getElement( root.getName() ); } } } return new RootPair( typeRoot, root ); } /** * Get the named option for this page. * * @param name The name of the option to look up * @return The value of the option, or null if it is not set */ public String getOption( String name ) { return getContentElement().getAttributeValue( name ); } /** * Set an option on this page. * * @param name The name of the option to set * @param value The value to set the option to */ public void setOption( String name, String value ) { getContentElement().setAttribute( name, value ); } public List getSupportedOptions( UserData user ) { try { return getType( user ).getSupportedOptions(); } catch ( Exception e ) { e.printStackTrace(); return new Vector(); } } public DocElement getType( UserData user ) { return loadType( getContentElement().getName(), getPage(), user ); } public static String getTypeClass( String typeName ) { Type type = Type.getType( typeName ); if ( type == null ) { return ""; } return type.getClassName(); } public static String getTypeDescription( String typeName ) { Type type = Type.getType( typeName ); if ( type == null ) { return ""; } return type.getDescription(); } private DocElement loadType( String type, DocumentPage page, UserData user ) { try { Class typeClass = Class.forName( getTypeClass( type ) ); Constructor con = typeClass.getConstructor( new Class[]{ java.lang.String.class} ); DocElement ret = (DocElement) con.newInstance( new Object[]{type} ); ret.setPage( page ); ret.setUser( user ); ret.setDoc( this ); return ret; } catch ( Exception e ) { System.err.println( "Error, doctype " + type + " could not be found" ); e.printStackTrace(); return null; } } public static String encode( String in ) { if ( in == null ) { return null; } char[] ret = new char[in.length()]; in.getChars( 0, in.length(), ret, 0 ); for ( int i = 0; i < ret.length; i++ ) { if ( ret[i] >= 126 ) { ret[i] = '_'; } switch ( ret[i] ) { case ' ': case '/': case '&': case '?': case '+': case '"': case '\'': ret[i] = '_'; break; default: } } return new String( ret ); } public static class GenerationException extends Exception { public GenerationException( String message ) { super( message ); } } } class RootPair { private DocElement type; private Element root; public RootPair( DocElement type, Element root ) { this.type = type; this.root = root; } public DocElement getType() { return type; } public Element getData() { return root; } }