You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by bo...@apache.org on 2013/09/22 23:33:10 UTC

svn commit: r1525452 [2/2] - in /tapestry/tapestry-site/trunk: ./ src/main/java/org/apache/cxf/cwiki/ template/

Modified: tapestry/tapestry-site/trunk/src/main/java/org/apache/cxf/cwiki/SiteExporter.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry-site/trunk/src/main/java/org/apache/cxf/cwiki/SiteExporter.java?rev=1525452&r1=1525451&r2=1525452&view=diff
==============================================================================
--- tapestry/tapestry-site/trunk/src/main/java/org/apache/cxf/cwiki/SiteExporter.java (original)
+++ tapestry/tapestry-site/trunk/src/main/java/org/apache/cxf/cwiki/SiteExporter.java Sun Sep 22 21:33:09 2013
@@ -19,11 +19,11 @@
 
 package org.apache.cxf.cwiki;
 
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.FileWriter;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
@@ -47,12 +47,16 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.xml.datatype.DatatypeFactory;
 import javax.xml.datatype.XMLGregorianCalendar;
 import javax.xml.namespace.QName;
+import javax.xml.parsers.ParserConfigurationException;
 import javax.xml.ws.AsyncHandler;
 import javax.xml.ws.Dispatch;
 import javax.xml.ws.Response;
@@ -81,7 +85,7 @@ import org.apache.cxf.transport.http.HTT
 import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
 import org.apache.velocity.Template;
 import org.apache.velocity.VelocityContext;
-import org.apache.velocity.app.Velocity;
+import org.apache.velocity.app.VelocityEngine;
 import org.apache.velocity.runtime.resource.loader.URLResourceLoader;
 import org.ccil.cowan.tagsoup.Parser;
 import org.ccil.cowan.tagsoup.XMLWriter;
@@ -93,18 +97,19 @@ public class SiteExporter implements Run
 
     static final String HOST = "https://cwiki.apache.org";
     static final String ROOT = HOST + "/confluence";
-    static final String RPC_ROOT = "/rpc/soap-axis/confluenceservice-v1";
+    static final String RPC_ROOT = "/rpc/soap-axis/confluenceservice-v";
     static final String SOAPNS = "http://soap.rpc.confluence.atlassian.com";
     
+    static final String SEPARATOR = " > ";
     
-    static final String CONFLUENCE_IMAGES = "images/confluence";
-
     
     
     static boolean debug;
     static String userName = "cxf-export-user";
     static String password;
     
+    static int apiVersion = 1;
+    
     static boolean svn;
     static boolean commit;
     static StringBuilder svnCommitMessage = new StringBuilder();
@@ -113,11 +118,16 @@ public class SiteExporter implements Run
     static String loginToken;
     static Dispatch<Document> dispatch;
     static AtomicInteger asyncCount = new AtomicInteger();
+    static Map<String, Space> spaces = new ConcurrentHashMap<String, Space>();
+    static List<SiteExporter> siteExporters;
 
     Map<String, Page> pages = new ConcurrentHashMap<String, Page>();
     Collection<Page> modifiedPages = new ConcurrentLinkedQueue<Page>();
     Set<String> globalPages = new CopyOnWriteArraySet<String>();
-    Set<String> blogPages = new CopyOnWriteArraySet<String>();
+    
+    Map<String, BlogEntrySummary> blog = new ConcurrentHashMap<String, BlogEntrySummary>();
+    Set<BlogEntrySummary> modifiedBlog = new CopyOnWriteArraySet<BlogEntrySummary>();
+    
 
     String spaceKey = "CXF";
     String pageCacheFile = "pagesConfig.obj";
@@ -129,7 +139,9 @@ public class SiteExporter implements Run
     File outputDir = rootOutputDir;
 
     Template template;
+    Space space;
     
+
     public SiteExporter(String fileName, boolean force) throws Exception {
         forceAll = force;
         
@@ -159,29 +171,25 @@ public class SiteExporter implements Run
             String[] pgs = globals.split(",");
             globalPages.addAll(Arrays.asList(pgs));
         }
-        if (props.containsKey("blogPages")) {
-            String blogpages = props.getProperty("blogPages");
-            String[] pgs = blogpages.split(",");
-            blogPages.addAll(Arrays.asList(pgs));
-        }
         
         props = new Properties();
         String clzName = URLResourceLoader.class.getName();
         props.put("resource.loader", "url");
         props.put("url.resource.loader.class", clzName);
         props.put("url.resource.loader.root", "");
-        synchronized (Velocity.class) {
-            Velocity.init(props);
+        
+        VelocityEngine engine = new VelocityEngine();
+        engine.init(props);
             
-            URL url = ClassLoaderUtils.getResource(templateName, this.getClass());
-            if (url == null) {
-                File file = new File(templateName);
-                if (file.exists()) {
-                    url = file.toURI().toURL();
-                }
+        URL url = ClassLoaderUtils.getResource(templateName, this.getClass());
+        if (url == null) {
+            File file = new File(templateName);
+            if (file.exists()) {
+                url = file.toURI().toURL();
             }
-            template = Velocity.getTemplate(url.toURI().toString());
         }
+        template = engine.getTemplate(url.toURI().toString());
+               
         outputDir.mkdirs();
     }
     
@@ -190,7 +198,7 @@ public class SiteExporter implements Run
             Service service = Service.create(new QName(SOAPNS, "Service"));
             service.addPort(new QName(SOAPNS, "Port"), 
                             SOAPBinding.SOAP11HTTP_BINDING,
-                            ROOT + RPC_ROOT);
+                            ROOT + RPC_ROOT + apiVersion);
     
             dispatch = service.createDispatch(new QName(SOAPNS, "Port"), 
                                               Document.class, Service.Mode.PAYLOAD);
@@ -216,7 +224,7 @@ public class SiteExporter implements Run
     
     public void run() {
         try {
-            doExport();
+            render();
         } catch (Exception e) {
             e.printStackTrace();
         }
@@ -226,29 +234,70 @@ public class SiteExporter implements Run
         Page p = findPage(s);
         if (p != null) {
             pages.remove(p.getId());
-            modifiedPages.add(p);
+            if (!modifiedPages.contains(p)) {
+                modifiedPages.add(p);
+            }
         }
     }
     
-    public void doExport() throws Exception {
+    /**
+     * @return true if some pages have changed - rendering is needed
+     * @throws Exception
+     */
+    public boolean initialize() throws Exception {
         if (!forceAll) {
-            loadPagesCache();
+            loadCache();
         }
-
+        
         // debug stuff, force regen of a page
         //forcePage("Navigation");
         //forcePage("Index");
         //forcePage("JavaDoc");
         //forcePage("DOSGi Architecture");
+        //forcePage("Book In One Page");
         
         if (modifiedPages.isEmpty() && checkRSS()) {
             System.out.println("(" + spaceKey + ") No changes detected from RSS");
-            return;
+            return false;
         }
-        
+
         doLogin();
+        checkVersion();
+        getSpace();
+        if ("-space-".equals(breadCrumbRoot)) {
+            breadCrumbRoot = space.getName();
+        }
+        loadBlog();
         loadPages();
         
+        return true;
+    }
+        
+    private void checkVersion() throws ParserConfigurationException, IOException {
+        Document doc = DOMUtils.createDocument();
+        Element el = doc.createElementNS(SOAPNS, "ns1:getServerInfo");
+        Element el2 = doc.createElement("in0");
+        el.appendChild(el2);
+        el2.setTextContent(loginToken);
+        doc.appendChild(el);
+
+        doc = getDispatch().invoke(doc);
+        el = DOMUtils.getFirstElement(DOMUtils.getFirstElement(doc.getDocumentElement()));
+        while (el != null) {
+            if ("majorVersion".equals(el.getLocalName())) {
+                String major = DOMUtils.getContent(el);
+                if (Integer.parseInt(major) >= 5) {
+                    apiVersion = 2;
+                    ((java.io.Closeable)dispatch).close();
+                    dispatch = null;
+                }
+            }
+              
+            el = DOMUtils.getNextElement(el);
+        }
+    }
+
+    protected void render() throws Exception {
         for (Page p : modifiedPages) {
             if (globalPages.contains(p.getTitle())) {
                 modifiedPages.clear();
@@ -256,18 +305,27 @@ public class SiteExporter implements Run
                 break;
             }
         }
+        
         if (forceAll) {
             modifiedPages.clear();
             modifiedPages.addAll(pages.values());
+            
+            modifiedBlog.clear();
+            modifiedBlog.addAll(blog.values());
         }
-
-        
-        if (!modifiedPages.isEmpty()) {
+        if (!modifiedBlog.isEmpty()) {
+            //blogs changed, see if any pages have blogs
+            for (Page p : pages.values()) {
+                if (p.hasBlog() && !modifiedPages.contains(p)) {
+                    modifiedPages.add(p);
+                }
+            }
+        }
+        if (!modifiedPages.isEmpty() || !modifiedBlog.isEmpty()) {
+            renderBlog();
             renderPages();
-            savePages();
+            saveCache();
         }
-        
-
     }
 
 
@@ -286,15 +344,15 @@ public class SiteExporter implements Run
         List<Element> els = DOMUtils.getChildrenWithName(doc.getDocumentElement(),
                                                         "http://www.w3.org/2005/Atom", 
                                                         "entry");
-        //XMLUtils.printDOM(doc);
+        // XMLUtils.printDOM(doc);
         for (Element el : els) {
             Element e2 = DOMUtils.getFirstChildWithName(el, "http://www.w3.org/2005/Atom", "updated");
             String val = DOMUtils.getContent(e2);
             XMLGregorianCalendar cal = DatatypeFactory.newInstance().newXMLGregorianCalendar(val);
             e2 = DOMUtils.getFirstChildWithName(el, "http://www.w3.org/2005/Atom", "title");
             String title = DOMUtils.getContent(e2);
-            Page p = findPage(title);
             
+            Page p = findPage(title);
             if (p != null) {
                 //found a modified page - need to rebuild
                 if (cal.compare(p.getModifiedTime()) > 0) {
@@ -302,24 +360,36 @@ public class SiteExporter implements Run
                     return false;
                 }
             } else {
-                System.out.println("(" + spaceKey + ") Did not find page for: " + title);
-                return false;
+                BlogEntrySummary entry = findBlogEntry(title);
+                if (entry != null) {
+                    // we don't have modified date so just assume it's modified
+                    // we'll use version number to actually figure out if page is modified or not
+                    System.out.println("(" + spaceKey + ") Possible changed blog page found: " + title);
+                    return false;
+                } else {
+                    System.out.println("(" + spaceKey + ") Did not find page for: " + title);
+                    return false;
+                }
             }
         }
         
         return true;
     }
 
-    private void savePages() throws Exception {
+    private void saveCache() throws Exception {
         File file = new File(rootOutputDir, pageCacheFile);
         file.getParentFile().mkdirs();
         FileOutputStream fout = new FileOutputStream(file);
         ObjectOutputStream oout = new ObjectOutputStream(fout);
         oout.writeObject(pages);
+        oout.writeObject(blog);
         oout.close();
     }
 
     private void renderPages() throws Exception {
+        PageManager pageManager = new PageManager(this);
+        Renderer renderer = new Renderer(this);
+        
         int total = modifiedPages.size();
         int count = 0;
         for (Page p : modifiedPages) {
@@ -330,9 +400,13 @@ public class SiteExporter implements Run
             
             loadPageContent(p, null, null);
             VelocityContext ctx = new VelocityContext();
-            ctx.put("exporter", this);
+            ctx.put("autoexport", this);
             ctx.put("page", p);
+            ctx.put("body", p.getContent());
             ctx.put("confluenceUri", ROOT);
+            ctx.put("pageManager", pageManager);
+            ctx.put("renderer", renderer);
+            ctx.put("exporter", this);
             
             File file = new File(outputDir, p.createFileName());
             boolean isNew = !file.exists();
@@ -348,8 +422,56 @@ public class SiteExporter implements Run
             } else {
                 svnCommitMessage.append("Modified: " + file.getName() + "\n");                
             }
+            
+            p.setContent(null);
+        }
+    }
+    
+    private void renderBlog() throws Exception {
+        PageManager pageManager = new PageManager(this);
+        Renderer renderer = new Renderer(this);
+        
+        int total = modifiedBlog.size();
+        int count = 0;
+        for (BlogEntrySummary entry : modifiedBlog) {
+            count++;
+            System.out.println("(" + spaceKey + ") Rendering Blog Entry " + entry.getTitle() 
+                               + "    (" + count + "/" + total + ")");
+            
+            loadAttachments(entry);
+            String body = renderPage(entry);
+            body = updateContentLinks(entry, body, null, mainDivClass);
+            
+            pageManager.setDirectory(entry.getDirectory());
+            
+            VelocityContext ctx = new VelocityContext();
+            ctx.put("autoexport", this);
+            ctx.put("page", entry);
+            ctx.put("body", body);
+            ctx.put("confluenceUri", ROOT);
+            ctx.put("pageManager", pageManager);
+            ctx.put("renderer", renderer);
+            ctx.put("exporter", this);
+            ctx.put("isBlogEntry", Boolean.TRUE);
+            
+            File file = new File(outputDir, entry.getPath());
+            file.getParentFile().mkdirs();
+            boolean isNew = !file.exists();
+            
+            FileWriter writer = new FileWriter(file);
+            ctx.put("out", writer);
+            template.merge(ctx, writer);
+            writer.close();
+            if (isNew) {
+                //call "svn add"
+                callSvn("add", file.getAbsolutePath());
+                svnCommitMessage.append("Adding: " + file.getName() + "\n");
+            } else {
+                svnCommitMessage.append("Modified: " + file.getName() + "\n");                
+            }
         }
     }
+    
     void callSvn(String ... commands) throws Exception {
         callSvn(outputDir, commands);
     }
@@ -367,8 +489,8 @@ public class SiteExporter implements Run
         }
     }
     
-    private void loadAttachments(Page p) throws Exception {
-        Document doc = XMLUtils.newDocument();
+    private void loadAttachments(AbstractPage p) throws Exception {
+        Document doc = DOMUtils.createDocument();
         Element el = doc.createElementNS(SOAPNS, "ns1:getAttachments");
         Element el2 = doc.createElement("in0");
         el.appendChild(el2);
@@ -389,7 +511,7 @@ public class SiteExporter implements Run
                 
                 p.addAttachment(aid, filename);
                 
-                String dirName = p.createFileName();
+                String dirName = p.getPath();
                 dirName = dirName.substring(0, dirName.lastIndexOf(".")) + ".data";
                 File file = new File(outputDir, dirName);
                 if (!file.exists()) {
@@ -417,37 +539,20 @@ public class SiteExporter implements Run
             el = DOMUtils.getNextElement(el);
         }
     }
-    String loadIcon(String href) throws Exception {
-        
-        String filename = href.replace("/confluence/images", "images/confluence");
-        File file = new File(outputDir + filename);
-        if(file.exists())
-            return file.getName();
-        if(!file.getParentFile().exists()) {
-            file.getParentFile().mkdirs();
-        }
-        FileOutputStream out = new FileOutputStream(file);
-        URL url = new URL(HOST + href);
-        InputStream ins = url.openStream();
-        IOUtils.copy(ins, out);
-        out.close();
-        ins.close();
-        return file.getName();
-    }
-    String loadUserImage(Page p, String href) throws Exception {
+    String loadUserImage(AbstractPage p, String href) throws Exception {
         return loadPageBinaryData(p, href, "userimage", true);
     }
-    String loadThumbnail(Page p, String href) throws Exception {
+    String loadThumbnail(AbstractPage p, String href) throws Exception {
         return loadPageBinaryData(p, href, "thumbs", false);
     }
-    String loadPageBinaryData(Page p, String href, String type, boolean auth) throws Exception {
+    String loadPageBinaryData(AbstractPage p, String href, String type, boolean auth) throws Exception {
         String filename = href.substring(href.lastIndexOf('/') + 1);
         filename = filename.replace(' ', '_');
         if (filename.indexOf('?') != -1) {
             filename = filename.substring(0, filename.indexOf('?'));
         }
         
-        String dirName = p.createFileName();
+        String dirName = p.getPath();
         dirName = dirName.substring(0, dirName.lastIndexOf(".")) + "." + type;
         File file = new File(outputDir, dirName);
         if (!file.exists()) {
@@ -490,21 +595,13 @@ public class SiteExporter implements Run
         }
     }
     public Page findPage(String title) throws Exception {
-        for (Page p : pages.values()) {
-            if (title.equals(p.getTitle())) {
-                return p;
-            }
-        }
-        return null;
+        return (Page) findByTitle(title, pages.values());
     }
+    
     public Page findPageByURL(String url) throws Exception {
-        for (Page p : pages.values()) {
-            if (p.getURL().endsWith(url)) {
-                return p;
-            }
-        }
-        return null;
+        return (Page) findByURL(url, pages.values());
     }
+    
     public Page findPageByID(String id) {
         for (Page p : pages.values()) {
             if (p.getId().equals(id)) {
@@ -513,14 +610,46 @@ public class SiteExporter implements Run
         }
         return null;
     }
-    public String breadcrumbs(Page page) {
-        String separator = "&gt;";
-        String s = "&nbsp;" + separator + "&nbsp;";
 
+    public String breadcrumbs(BlogEntrySummary page) {
+        StringBuffer buffer = new StringBuffer();
+        if (breadCrumbRoot != null) {
+            buffer.append("<a href=\"");
+            buffer.append("../../../index.html");
+            buffer.append("\">");
+            buffer.append(breadCrumbRoot);
+            buffer.append("</a>");
+            buffer.append(SEPARATOR);
+        } else {
+            buffer.append("<a href=\"../../../index.html\">Index</a>");
+            buffer.append(SEPARATOR);
+        }
+        XMLGregorianCalendar published = page.getPublished();
+        buffer.append(String.valueOf(published.getYear()));
+        buffer.append(SEPARATOR);
+        if (published.getMonth() < 10) {
+            buffer.append("0");
+        } 
+        buffer.append(String.valueOf(published.getMonth()));
+        buffer.append(SEPARATOR);
+        if (published.getDay() < 10) {
+            buffer.append("0");
+        } 
+        buffer.append(String.valueOf(published.getDay()));
+        buffer.append(SEPARATOR);
+        buffer.append("<a href=\"");
+        buffer.append(page.createFileName());
+        buffer.append("\">");
+        buffer.append(page.getTitle());
+        buffer.append("</a>");
+        return buffer.toString();
+    }
+    
+    public String breadcrumbs(Page page) {
         StringBuffer buffer = new StringBuffer();
         List<Page> p = new LinkedList<Page>();
         String parentId = page.getParentId();
-        Page parent = parentId == null ? null : pages.get(parentId);
+        Page parent = pages.get(parentId);
         while (parent != null) {
             p.add(0, parent);
             parentId = parent.getParentId();
@@ -532,7 +661,7 @@ public class SiteExporter implements Run
             buffer.append("\">");
             buffer.append(breadCrumbRoot);
             buffer.append("</a>");
-            buffer.append(s);
+            buffer.append(SEPARATOR);
         }
         for (Page p2 : p) {
             buffer.append("<a href=\"");
@@ -540,7 +669,7 @@ public class SiteExporter implements Run
             buffer.append("\">");
             buffer.append(p2.getTitle());
             buffer.append("</a>");
-            buffer.append(s);
+            buffer.append(SEPARATOR);
         }
         buffer.append("<a href=\"");
         buffer.append(page.createFileName());
@@ -575,7 +704,19 @@ public class SiteExporter implements Run
         }
         return p.getContent();
     }
-    private String loadPageContent(Page p, String divId, String divCls) throws Exception {
+    protected String loadPageContent(Page p, String divId, String divCls) throws Exception {
+        String content = renderPage(p);
+        content = updateContentLinks(p, content, divId, 
+                                     divCls == null && divId == null ? mainDivClass : divCls);
+        if (divId == null) {
+            p.setContent(content);
+        } else {
+            p.setContentForDivId(divId, content);
+        }
+        return content;
+    }
+
+    private String renderPage(AbstractPage p) throws ParserConfigurationException {
         Document doc = XMLUtils.newDocument();
         Element el = doc.createElementNS(SOAPNS, "ns1:renderContent");
         Element el2 = doc.createElement("in0");
@@ -609,24 +750,19 @@ public class SiteExporter implements Run
         doc.appendChild(el);
         doc = getDispatch().invoke(doc);
         
-        String content = doc.getDocumentElement().getFirstChild().getTextContent().trim();
-        content = updateContentLinks(p, content, divId, 
-                                     divCls == null && divId == null ? mainDivClass : divCls);
-        if (divId == null) {
-            p.setContent(content);
-        } else {
-            p.setContentForDivId(divId, content);
-        }
-        return content;
+        return doc.getDocumentElement().getFirstChild().getTextContent().trim();
     }
 
     public String unwrap(String v) throws Exception {
+        if (v == null) {
+            return null;
+        }
         return v.trim().replaceFirst("^<div[^>]*>", "").replaceFirst("</div>$", "");
     }
 
     private static synchronized void doLogin() throws Exception {
         if (loginToken == null) {
-            Document doc = XMLUtils.newDocument();
+            Document doc = DOMUtils.createDocument();
             Element el = doc.createElementNS(SOAPNS, "ns1:login");
             Element el2 = doc.createElement("in0");
             
@@ -651,85 +787,138 @@ public class SiteExporter implements Run
         }
     }
 
-    public void loadPagesCache() throws Exception {
+    public void loadCache() throws Exception {
         File file = new File(rootOutputDir, pageCacheFile);
         if (file.exists()) {
-            FileInputStream fin = new FileInputStream(file);
-            ObjectInputStream oin = new ObjectInputStream(fin);
-            pages = CastUtils.cast((Map<?, ?>)oin.readObject());
-            oin.close();
+            try {
+                FileInputStream fin = new FileInputStream(file);
+                ObjectInputStream oin = new ObjectInputStream(fin);
+                pages = CastUtils.cast((Map<?, ?>)oin.readObject());
+                blog = CastUtils.cast((Map<?, ?>)oin.readObject());
+                oin.close();
+                
+                for (Page p : pages.values()) {
+                    p.setExporter(this);
+                }
+            } catch (Throwable t) {
+                //invalid cache, punt
+                pages.clear();
+                blog.clear();
+            }
         }
     }
     
-    private Document getPagesDocument() throws Exception {
-        return getElementsDocument("ns1:getPages");
-    }
-    
-    private Document getBlogEntriesDocument() throws Exception {
-        return getElementsDocument("ns1:getBlogEntries");
-    }
-    
-    private Document getElementsDocument(String function) throws Exception {
+    public int getBlogVersion(String pageId) throws Exception {
         Document doc = XMLUtils.newDocument();
-        Element el = doc.createElementNS(SOAPNS, function);
+        Element el = doc.createElementNS(SOAPNS, "ns1:getBlogEntry");
         Element el2 = doc.createElement("in0");
         el.appendChild(el2);
         el2.setTextContent(loginToken);
         el2 = doc.createElement("in1");
         el.appendChild(el2);
-        el2.setTextContent(spaceKey);
+        el2.setTextContent(pageId);
         doc.appendChild(el);
         doc = getDispatch().invoke(doc);
-        return doc;
-    }
-    
-    private void loadAndAddPages(List<Future<?>> futures, Set<String> allPages, Set<Page> newPages) throws Exception {
-        Document doc = getPagesDocument();
-        ElementLoader loader = new ElementLoader()
-        {
-            public Future<?> loadElement(Element element, Set<String> allPages, Set<Page> newPages) throws Exception
-            {
-                return loadPage(element, allPages, newPages);
-            }
-        };
-        loadAndAddElements(futures, loader, doc, allPages, newPages);
-    }
-
-    private void loadAndAddBlogEntries(List<Future<?>> futures, Set<String> allPages, Set<Page> newPages) throws Exception {
-        Document doc = getBlogEntriesDocument();
-        ElementLoader loader = new ElementLoader()
-        {
-            public Future<?> loadElement(Element element, Set<String> allPages, Set<Page> newPages) throws Exception
-            {
-                return loadBlogEntry(element, allPages, newPages);
-            }
-        };
-        loadAndAddElements(futures, loader, doc, allPages, newPages);
+        
+        Node nd = doc.getDocumentElement().getFirstChild();
+        
+        String version = DOMUtils.getChildContent(nd, "version");
+        return Integer.parseInt(version);
     }
     
-    private void loadAndAddElements(List<Future<?>> futures, ElementLoader loader, Document doc,
-            Set<String> allPages, Set<Page> newPages) throws Exception {
+    public void loadBlog() throws Exception {
+        Document doc = DOMUtils.createDocument();
+        Element el = doc.createElementNS(SOAPNS, "ns1:getBlogEntries");
+        Element el2 = doc.createElement("in0");
+        el.appendChild(el2);
+        el2.setTextContent(loginToken);
+        el2 = doc.createElement("in1");
+        el.appendChild(el2);
+        el2.setTextContent(spaceKey);
+        doc.appendChild(el);
+        doc = getDispatch().invoke(doc);
+        
+        Map<String, BlogEntrySummary> oldBlog = new ConcurrentHashMap<String, BlogEntrySummary>(blog);
+        
         Node nd = doc.getDocumentElement().getFirstChild().getFirstChild();
         while (nd != null) {
             if (nd instanceof Element) {
-                futures.add(loader.loadElement((Element)nd, allPages, newPages));
+                BlogEntrySummary entry = new BlogEntrySummary((Element)nd);
+                entry.setVersion(getBlogVersion(entry.id));
+                BlogEntrySummary oldEntry = blog.put(entry.getId(), entry);
+                if (oldEntry == null || oldEntry.getVersion() != entry.getVersion()) {
+                    modifiedBlog.add(entry);
+                }
+                oldBlog.remove(entry.getId());
             }
             nd = nd.getNextSibling();
         }
+        
+        for (String id : oldBlog.keySet()) {
+            //these pages have been deleted
+            BlogEntrySummary p = blog.remove(id);
+            File file = new File(outputDir, p.getPath());
+            if (file.exists()) {
+                callSvn("rm", file.getAbsolutePath());
+                svnCommitMessage.append("Deleted: " + file.getName() + "\n");                
+            }
+            if (file.exists()) {
+                file.delete();
+            }            
+        }
+    }
+        
+    public BlogEntrySummary findBlogEntry(String title) throws Exception {
+        return (BlogEntrySummary) findByTitle(title, blog.values());
     }
     
-    private interface ElementLoader {
-        Future<?> loadElement(Element element, Set<String> allPages, Set<Page> newPages) throws Exception;
+    public BlogEntrySummary findBlogEntryByURL(String url) throws Exception {
+        return (BlogEntrySummary) findByURL(url, blog.values());
+    }
+    
+    private static AbstractPage findByURL(String url, Collection<? extends AbstractPage> pages) throws Exception {
+        for (AbstractPage p : pages) {
+            if (p.getURL().endsWith(url)) {
+                return p;
+            }
+        }
+        return null;
+    }
+    
+    private static AbstractPage findByTitle(String title, Collection<? extends AbstractPage> pages) throws Exception {
+        for (AbstractPage p : pages) {
+            if (title.equals(p.getTitle())) {
+                return p;
+            }
+        }
+        return null;
     }
     
     public void loadPages() throws Exception {
+        Document doc = XMLUtils.newDocument();
+        Element el = doc.createElementNS(SOAPNS, "ns1:getPages");
+        Element el2 = doc.createElement("in0");
+        el.appendChild(el2);
+        el2.setTextContent(loginToken);
+        el2 = doc.createElement("in1");
+        el.appendChild(el2);
+        el2.setTextContent(spaceKey);
+        doc.appendChild(el);
+        doc = getDispatch().invoke(doc);
+        
         Set<String> allPages = new CopyOnWriteArraySet<String>(pages.keySet());
         Set<Page> newPages = new CopyOnWriteArraySet<Page>();
         List<Future<?>> futures = new ArrayList<Future<?>>(allPages.size());
-
-        loadAndAddPages(futures, allPages, newPages);
-        loadAndAddBlogEntries(futures, allPages, newPages);
         
+        // XMLUtils.printDOM(doc.getDocumentElement());
+
+        Node nd = doc.getDocumentElement().getFirstChild().getFirstChild();
+        while (nd != null) {
+            if (nd instanceof Element) {
+                futures.add(loadPage((Element)nd, allPages, newPages));
+            }
+            nd = nd.getNextSibling();
+        }
         for (Future<?> f : futures) {
             //wait for all the pages to be done
             f.get();
@@ -755,6 +944,7 @@ public class SiteExporter implements Run
         while (checkIncludes()) {
             // nothing
         }
+        
     }
 
     public boolean checkIncludes() {
@@ -777,8 +967,7 @@ public class SiteExporter implements Run
         return false;
     }
     public void checkForChildren(Page p) {
-        String parentId = p.getParentId();
-        Page parent = parentId == null ? null : pages.get(parentId);
+        Page parent = pages.get(p.getParentId());
         int d = 1;
         while (parent != null) {
             for (Page p2 : pages.values()) {
@@ -792,6 +981,32 @@ public class SiteExporter implements Run
         }
     }
     
+    public static synchronized Space getSpace(String key) { 
+        Space space = spaces.get(key);
+        if (space == null) {
+            try {
+                doLogin();
+                
+                Document doc = XMLUtils.newDocument();
+                Element el = doc.createElementNS(SOAPNS, "ns1:getSpace");
+                Element el2 = doc.createElement("in0");
+                el.appendChild(el2);
+                el2.setTextContent(loginToken);
+                el2 = doc.createElement("in1");
+                el.appendChild(el2);
+                el2.setTextContent(key);
+                doc.appendChild(el);
+                
+                Document out = getDispatch().invoke(doc);
+                space = new Space(out);
+                spaces.put(key, space);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return space;
+    }
+    
     public Future<?> loadPage(Element pageSumEl,
                          final Set<String> allPages,
                          final Set<Page> newPages) throws Exception {
@@ -805,45 +1020,20 @@ public class SiteExporter implements Run
         el2.setTextContent(DOMUtils.getChildContent(pageSumEl, "id"));
         doc.appendChild(el);
         
-        return getResponseHandler(doc, allPages, newPages, false);
-    }
-    
-    public Future<?> loadBlogEntry(Element pageSumEl, final Set<String> allPages,
-            final Set<Page> newPages) throws Exception
-    {
-        Document doc = XMLUtils.newDocument();
-        Element el = doc.createElementNS(SOAPNS, "ns1:getBlogEntry");
-        Element el2 = doc.createElement("in0");
-        el.appendChild(el2);
-        el2.setTextContent(loginToken);
-        el2 = doc.createElement("in1");
-        el.appendChild(el2);
-        el2.setTextContent(DOMUtils.getChildContent(pageSumEl, "id"));
-        doc.appendChild(el);
-        
-        return getResponseHandler(doc, allPages, newPages, true);
-    }
-    
-    private Future<?> getResponseHandler(Document doc, final Set<String> allPages, final Set<Page> newPages, final boolean blogPost) throws Exception
-    {
-        // make sure we only fire off about 15-20 or confluence may get a bit overloaded
-        while (asyncCount.get() > 15)
-        {
+        //make sure we only fire off about 15-20 or confluence may get a bit overloaded
+        while (asyncCount.get() > 15) {
             Thread.sleep(10);
         }
         asyncCount.incrementAndGet();
-        return getDispatch().invokeAsync(doc, new AsyncHandler<Document>() {
+        Future<?> f = getDispatch().invokeAsync(doc, new AsyncHandler<Document>() {
             public void handleResponse(Response<Document> doc) {
                 try {
-                    Page page = new Page(doc.get());
+                    Page page = new Page(doc.get(), SiteExporter.this);
+                    page.setExporter(SiteExporter.this);
                     Page oldPage = pages.put(page.getId(), page);
                     if (oldPage == null || page.getModifiedTime().compare(oldPage.getModifiedTime()) > 0) {
-                        modifiedPages.add(page);
-                        if(blogPost) {
-                            // if we are adding or modifying a blog post, make sure the pages containing them get re-rendered
-                            for (String title : blogPages) {
-                                modifiedPages.add(findPage(title));
-                            }
+                        if (!modifiedPages.contains(page)) {
+                            modifiedPages.add(page);
                         }
                         if (oldPage == null) {
                             //need to check parents to see if it has a {children} tag so we can re-render
@@ -860,9 +1050,10 @@ public class SiteExporter implements Run
                 }
             }
         });
-    }
+        return f;
+    }    
     
-    private String updateContentLinks(Page page, String content,
+    private String updateContentLinks(AbstractPage page, String content,
                                       String id, String divCls) throws Exception {
         XMLReader parser = createTagSoupParser();
         StringWriter w = new StringWriter();
@@ -871,6 +1062,14 @@ public class SiteExporter implements Run
         content = w.toString();
         content = content.substring("<html><body>".length());
         content = content.substring(0, content.lastIndexOf("</body></html>"));
+        
+        int idx = content.indexOf('>');
+        if (idx != -1
+            && content.substring(idx + 1).startsWith("<p></p>")) {
+            //new confluence tends to stick an empty paragraph at the beginning for some pages (like Banner)
+            //that causes major formatting issues.  Strip it.
+            content = content.substring(0, idx + 1) + content.substring(idx + 8);
+        }
         return content;
     }
     protected XMLReader createTagSoupParser() throws Exception {
@@ -886,7 +1085,7 @@ public class SiteExporter implements Run
 
         return reader;
     }
-    protected ContentHandler createContentHandler(final Page page, Writer w, 
+    protected ContentHandler createContentHandler(AbstractPage page, Writer w, 
                                                   String id, String divCls) {
         XMLWriter xmlWriter = new ConfluenceCleanupWriter(this, w, page, id, divCls);
         xmlWriter.setOutputProperty(XMLWriter.OMIT_XML_DECLARATION, "yes");
@@ -903,6 +1102,7 @@ public class SiteExporter implements Run
         ListIterator<String> it = Arrays.asList(args).listIterator();
         List<String> files = new ArrayList<String>();
         boolean forceAll = false;
+        int maxThreads = -1;
         while (it.hasNext()) {
             String s = it.next();
             if ("-debug".equals(s)) {
@@ -919,21 +1119,49 @@ public class SiteExporter implements Run
                 svn = true;
             } else if ("-commit".equals(s)) {
                 commit = true;
+            } else if ("-maxThreads".equals(s)) {
+                maxThreads = Integer.parseInt(it.next());
             } else if (s != null && s.length() > 0) {
                 files.add(s);
             }
         }
         
-        List<Thread> threads = new ArrayList<Thread>(files.size());
+        
+        List<SiteExporter> exporters = new ArrayList<SiteExporter>();
         for (String file : files) {
-            Thread t = new Thread(new SiteExporter(file, forceAll));
-            threads.add(t);
-            t.start();
+            exporters.add(new SiteExporter(file, forceAll));
         }
-        for (Thread t : threads) {
-            t.join();
+        List<SiteExporter> modified = new ArrayList<SiteExporter>();
+        for (SiteExporter exporter : exporters) {
+            if (exporter.initialize()) {
+                modified.add(exporter);
+            }
         }
         
+        // render stuff only if needed
+        if (!modified.isEmpty()) {
+            setSiteExporters(exporters);
+
+            if (maxThreads <= 0) {
+                maxThreads = modified.size();
+            }
+
+            ExecutorService executor = Executors.newFixedThreadPool(maxThreads, new ThreadFactory() {
+                    public Thread newThread(Runnable r) {
+                        Thread t = new Thread(r);
+                        t.setDaemon(true);
+                        return t;
+                    }
+                });
+            List<Future<?>> futures = new ArrayList<Future<?>>(modified.size());
+            for (SiteExporter exporter : modified) {
+                futures.add(executor.submit(exporter));
+            }
+            for (Future<?> t : futures) {
+                t.get();
+            }
+        }
+                
         if (commit) {
             File file = FileUtils.createTempFile("svncommit", "txt");
             FileWriter writer = new FileWriter(file);
@@ -943,4 +1171,56 @@ public class SiteExporter implements Run
             svnCommitMessage.setLength(0);
         }
     }
+
+    public boolean hasChildren(Page page) {
+        for (Page p : pages.values()) {
+            if (p == page) {
+                continue;
+            }
+            if (page.getId().equals(p.getParentId())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public List<Page> getChildren(Page page) {
+        List<Page> children = new ArrayList<Page>();
+        for (Page p : pages.values()) {
+            if (p == page) {
+                continue;
+            }
+            if (page.getId().equals(p.getParentId())) {
+                children.add(p);
+            }
+        }
+        return children;
+    }
+
+    public String link(Page page) {
+        return page.getLink();
+    }
+
+    public Space getSpace() {
+        if (space == null) {
+            space = getSpace(spaceKey);
+        }
+        return space;
+    }
+    
+    private static void setSiteExporters(List<SiteExporter> exporters) {
+        siteExporters = exporters;
+    }
+
+    public int getAPIVersion() {
+        return apiVersion;
+    }
+
+    public String stripHost(String value) {
+        if (value.startsWith(HOST)) {
+            value = value.substring(HOST.length());
+        }
+        return value;
+    }
+    
 }

Added: tapestry/tapestry-site/trunk/src/main/java/org/apache/cxf/cwiki/Space.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry-site/trunk/src/main/java/org/apache/cxf/cwiki/Space.java?rev=1525452&view=auto
==============================================================================
--- tapestry/tapestry-site/trunk/src/main/java/org/apache/cxf/cwiki/Space.java (added)
+++ tapestry/tapestry-site/trunk/src/main/java/org/apache/cxf/cwiki/Space.java Sun Sep 22 21:33:09 2013
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.cwiki;
+
+import org.w3c.dom.Document;
+
+import org.apache.cxf.helpers.DOMUtils;
+
+/**
+ * 
+ */
+public class Space {
+
+    final String key;
+    final String name;
+    final String url;
+    
+    public Space(Document doc) throws Exception {
+        // org.apache.cxf.helpers.XMLUtils.printDOM(doc.getDocumentElement());
+
+        key = DOMUtils.getChildContent(doc.getDocumentElement().getFirstChild(), "key");
+        name = DOMUtils.getChildContent(doc.getDocumentElement().getFirstChild(), "name");
+        url = DOMUtils.getChildContent(doc.getDocumentElement().getFirstChild(), "url");
+    }
+    
+    public String getKey() {
+        return key;
+    }
+    public String getName() {
+        return name;
+    }
+    public String getURL() {
+        return url;
+    }
+
+}

Propchange: tapestry/tapestry-site/trunk/src/main/java/org/apache/cxf/cwiki/Space.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: tapestry/tapestry-site/trunk/template/template.vm
URL: http://svn.apache.org/viewvc/tapestry/tapestry-site/trunk/template/template.vm?rev=1525452&r1=1525451&r2=1525452&view=diff
==============================================================================
--- tapestry/tapestry-site/trunk/template/template.vm (original)
+++ tapestry/tapestry-site/trunk/template/template.vm Sun Sep 22 21:33:09 2013
@@ -28,7 +28,23 @@
 #end
   </title>
   <link type="text/css" rel="stylesheet" href="/resources/space.css">
+
+#if($page.hasCode)
+  ## Link directly to Apache CXF site's CSS files for SyntaxHighlighter:
+  <link href='http://cxf.apache.org/resources/highlighter/styles/shCoreCXF.css' rel='stylesheet' type='text/css' />
+  <link href='http://cxf.apache.org/resources/highlighter/styles/shThemeCXF.css' rel='stylesheet' type='text/css' />
+  <script src='http://cxf.apache.org/resources/highlighter/scripts/shCore.js' type='text/javascript'></script>
+#foreach ($hscript in $page.CodeScripts)
+  <script src='http://cxf.apache.org/resources/highlighter/scripts/$hscript' type='text/javascript'></script>
+#end
+  <script type="text/javascript">
+  SyntaxHighlighter.defaults['toolbar'] = false;
+  SyntaxHighlighter.all();
+  </script>
+#end
+
   <link href="/styles/style.css" rel="stylesheet" type="text/css"/>
+
 </head>
 <body>
   <div class="wrapper bs">