PageRenderTime 29ms CodeModel.GetById 15ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/scalate-tool/src/main/scala/org/fusesource/scalate/tool/commands/ConfluenceExport.scala

http://github.com/scalate/scalate
Scala | 249 lines | 183 code | 31 blank | 35 comment | 21 complexity | 26f1700a3124ea788e43a407a8d0012d MD5 | raw file
  1/**
  2 * Copyright (C) 2009-2011 the original author or authors.
  3 * See the notice.md file distributed with this work for additional
  4 * information regarding copyright ownership.
  5 *
  6 * Licensed under the Apache License, Version 2.0 (the "License");
  7 * you may not use this file except in compliance with the License.
  8 * You may obtain a copy of the License at
  9 *
 10 *     http://www.apache.org/licenses/LICENSE-2.0
 11 *
 12 * Unless required by applicable law or agreed to in writing, software
 13 * distributed under the License is distributed on an "AS IS" BASIS,
 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15 * See the License for the specific language governing permissions and
 16 * limitations under the License.
 17 */
 18package org.fusesource.scalate.tool.commands
 19
 20import collection.JavaConversions
 21
 22import java.{util => ju, lang => jl}
 23import java.util.zip.ZipInputStream
 24import java.io.{FileInputStream, FileWriter, File, ByteArrayOutputStream}
 25import java.lang.StringBuilder
 26import org.apache.felix.gogo.commands.{Action, Option => option, Argument => argument, Command => command}
 27import org.apache.felix.service.command.CommandSession
 28import org.swift.common.soap.confluence.{RemotePage, RemotePageSummary, ConfluenceSoapService, ConfluenceSoapServiceServiceLocator}
 29import collection.mutable.{HashMap, ListBuffer}
 30import org.fusesource.scalate.util.IOUtil
 31
 32
 33/**
 34 * <p>
 35 * Adding a tool that allows you to export confluence sites.  Example usage:
 36 *
 37 * <code>confexport --user user --password pass https://cwiki.apache.org/confluence SM ./out</code>
 38 * 
 39 * </p>
 40 *
 41 * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
 42 */
 43@command(scope = "scalate", name = "confexport", description = "Exports a confluence space.")
 44class ConfluenceExport extends Action {
 45
 46  @argument(index = 0, required = true, name = "url", description = "The confluence base URL (e.g. https://cwiki.apache.org/confluence)")
 47  var url: String = "https://cwiki.apache.org/confluence"
 48  @argument(index = 1, required = true, name = "space", description = "The confluence space key")
 49  var space: String = "SM"
 50  @argument(index = 2, required = false, name = "target", description = "The target directory")
 51  var target: File = new File(".")
 52
 53  @option(name = "--user", description = "Login user id")
 54  var user: String = _
 55  @option(name = "--password", description = "Login password")
 56  var password: String = _
 57  @option(name = "--allow-spaces", description = "(no arg) Allow spaces and other irregular chars in filenames. Use only if the O/S supports spaces in file names (e.g. Windows).")
 58  var allow_spaces: Boolean = false
 59  @option(name = "--format", description = """The format of the downloaded pages. Possible values are:
 60page - for page files suitable for rendering in a Scalate web site. Suffix is .page
 61conf - for a plain confluence text file without metadata. Suffix is .conf""")
 62  var format: String = "page"
 63  @option(name = "--target-db", description = "(no arg) Generate a link database for DocBook.")
 64  var target_db: Boolean = false
 65  
 66  case class Node(summary:RemotePageSummary) {
 67    val children = ListBuffer[Node]()
 68  }
 69
 70  def execute(session: CommandSession): AnyRef = {
 71    def println(value:Any) = session.getConsole.println(value)
 72    this.execute(value => println(value) )
 73  }
 74
 75  def execute(println: String => Unit): AnyRef = {
 76
 77    import JavaConversions._
 78
 79    println("downloading space index...")
 80    var confluence = serviceSetup(url + defaultConfluenceServiceExtension)
 81    if( user!=null && password!=null ) {
 82      loginToken = confluence.login(user, password);
 83    }
 84    val pageList: java.util.List[RemotePageSummary]
 85        = confluence.getPages(loginToken,space).toList
 86
 87    var pageMap = Map( pageList.map(x=> (x.getId, Node(x))) : _ * )
 88    val rootNodes = ListBuffer[Node]()
 89
 90    // add each node to the appropriate child collection.
 91    for( (key,node) <- pageMap ) {
 92      node.summary.getParentId match {
 93        case 0 => rootNodes += node
 94        case parentId => pageMap.get(parentId).foreach( _.children += node )
 95      }
 96    }
 97
 98    def export(dir:File, nodes:ListBuffer[Node]):Int = {
 99      var rc = 0
100      dir.mkdirs
101      nodes.foreach { node=>
102        val sanitized_title = sanitize(node.summary.getTitle);
103        val page = confluence.getPage(loginToken, node.summary.getId);
104        var content:String = "";
105        var file_suffix = ".page";
106        if (format.equalsIgnoreCase("page")) {
107            file_suffix = ".page"
108            content = """---
109title: """+page.getTitle+"""
110page_version: """+page.getVersion+"""
111page_creator: """+page.getCreator+"""
112page_modifier: """+page.getModifier+"""
113--- pipeline:conf
114"""
115        }
116        else if (format.equalsIgnoreCase("conf")) {
117            file_suffix = ".conf"
118        }
119        content += page.getContent
120
121        val file = new File(dir, sanitized_title + file_suffix)
122        println("downloading: \u001B[1;32m"+file+"\u001B[0m")
123        IOUtil.writeText(file, content)
124        rc += 1
125        if (target_db) {
126          TargetDB.startDiv(
127              sanitized_title,  // targetptr (used in DocBook olinks)
128              page.getTitle     // Page title
129          )
130        }
131        if( !node.children.isEmpty ) {
132          rc += export(new File(dir, sanitized_title), node.children)
133        }
134        if (target_db) {
135          TargetDB.endDiv()
136        }
137      }
138      rc
139    }
140    
141    def sanitize(title:String): String = {
142        if (allow_spaces) {
143            title.replaceAll("\\\"", "")
144        }
145        else {
146            title.toLowerCase.replaceAll(" ","-").replaceAll("[^a-zA-Z_0-9\\-\\.]", "")
147        }
148    }
149
150    if (target_db) {
151      TargetDB.rootDir = target
152      TargetDB.init(space, space)
153    }
154    val total = export(target, rootNodes);
155    if (target_db) {
156      TargetDB.close()
157    }
158    println("Exported \u001B[1;32m%d\u001B[0m page(s)".format(total));
159    confluence.logout(loginToken)
160    null
161  }
162
163  //-----
164  // Definitions to set up the Confluence SOAP-RPC service
165  //
166  
167  var confluence: ConfluenceSoapService = null
168  var loginToken: String = null
169  val defaultConfluenceServiceExtension = "/rpc/soap-axis/confluenceservice-v1" // Confluence soap service
170
171  def serviceSetup(address: String): ConfluenceSoapService = {
172    var serviceLocator = new ConfluenceSoapServiceServiceLocator
173    serviceLocator.setConfluenceserviceV2EndpointAddress(address);
174    serviceLocator.getConfluenceserviceV2();
175  }
176
177  //-----
178  // The TargetDB object enables the generation of a DocBook
179  // link database, target.db. Using the link database, a DocBook
180  // document can then easily cross-reference a confluence page.
181  
182  object TargetDB {
183    var rootDir = new File(".")
184    var rootFileName = "index.html"
185    var targetDBFileName = "target.db"
186    var level: Int = 0
187    var targetContent: StringBuffer = new StringBuffer
188    var hrefStack = new java.util.Stack[String]
189    
190    def init(targetptr: String, title: String) {
191      targetContent.append(
192          "<div element=\"book\" href=\"" + rootFileName
193          + "\" number=\"\" targetptr=\""
194          + targetptr + "\">\n"
195          + "    <ttl>" + title + "</ttl>\n"
196          + "    <xreftext>" + title + "</xreftext>\n"
197          )
198      level = 1
199    }
200    
201    def close() {
202      while (level > 0) { endDiv() }
203      val file= new File(rootDir, targetDBFileName)
204      IOUtil.writeText(file, targetContent.toString())
205    }
206    
207    def startDiv(targetptr: String, title: String) {
208      val indent = "    " * level
209      var escTitle = escapeXml(title)
210      appendAndPush(hrefStack, targetptr)
211      var href = hrefStack.peek + ".html"
212      targetContent.append(
213            indent + "<div element=\"" + getDivElementName() + "\" href=\""
214                   + href + "\" number=\"\" targetptr=\""
215                   + targetptr + "\">\n"
216          + indent + "    <ttl>" + escTitle + "</ttl>\n"
217          + indent + "    <xreftext>" + escTitle + "</xreftext>\n"
218          )
219      level += 1
220    }
221  
222    def endDiv() {
223      level -= 1
224      if ( ! hrefStack.empty) hrefStack.pop
225      targetContent.append("    " * level + "</div>\n")
226    }
227    
228    protected def getDivElementName(): String = {
229      if (level==0) { "book" }
230      else if (level==1) { "chapter" }
231      else { "section" }
232    }
233    
234    protected def escapeXml(text: String): String = {
235      text.replaceAll("&", "&amp;").replaceAll("<", "&lt;")
236    }
237    
238    protected def appendAndPush(stack: java.util.Stack[String], segment: String) {
239      if (stack.empty) {
240        stack.push(segment)
241      }
242      else {
243        stack.push(stack.peek() + "/" + segment)
244      }
245    }
246    
247  }
248
249}