Skip to content

Commit

Permalink
JBEHAVE-994: Export Stories from Confluence
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Hug authored and Thomas Hug committed Feb 25, 2014
1 parent d86d539 commit 9b36294
Show file tree
Hide file tree
Showing 10 changed files with 599 additions and 7 deletions.
7 changes: 6 additions & 1 deletion jbehave-rest/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<version>3.10-SNAPSHOT</version>
</parent>
<artifactId>jbehave-rest</artifactId>
<packaging>maven-plugin</packaging>
<packaging>maven-plugin</packaging>
<name>JBehave REST</name>
<description>Extension of JBehave Core that supports REST APIs to access stories resources</description>

Expand All @@ -26,6 +26,11 @@
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.7.3</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package org.jbehave.core.io.rest.confluence;

import static java.text.MessageFormat.format;

import java.util.List;

import org.jbehave.core.io.rest.RESTClient;

import com.thoughtworks.xstream.XStream;

public class Confluence {

private static final String SEARCH_PAGE = "{0}/rest/prototype/1/search/name?query={2}&type=page&spaceKey={1}";
private static final String EXPAND_PAGE = "{0}?expand=children";
private static final String REGULAR_PAGE = "{0}";

private final RESTClient client;

public Confluence(RESTClient client) {
this.client = client;
}

public Page loadRootPage(String baseUrl, String spaceKey, String pageName) {
String searchResult = client.get(format(SEARCH_PAGE, baseUrl, spaceKey, pageName));
XStream parse = configureXStream();
Results results = (Results) parse.fromXML(searchResult);
return results.getGroup().getResult();
}

public Page loadPage(String pageUrl, boolean expanded) {
String pattern = expanded ? EXPAND_PAGE : REGULAR_PAGE;
String content = client.get(format(pattern, pageUrl));
XStream parse = configureXStream();
Page page = (Page) parse.fromXML(content);
return page;
}

protected XStream configureXStream() {
XStream stream = new XStream();
stream.addImplicitCollection(Page.class, "link");
stream.alias("results", Results.class);
stream.alias("result", Page.class);
stream.alias("content", Page.class);
stream.alias("link", Link.class);
stream.useAttributeFor(Link.class, "rel");
stream.useAttributeFor(Link.class, "href");
stream.ignoreUnknownElements();
return stream;
}

public static class Results {

private Group group;

public Group getGroup() {
return group;
}

public void setGroup(Group group) {
this.group = group;
}

}

public static class Group {

private Page result;

public Page getResult() {
return result;
}

public void setResult(Page result) {
this.result = result;
}

}

public static class Page {

private List<Link> link;
private String title;
private String body;
private List<Page> children;

public String getSelfReference() {
for (Link candidate : link) {
if ("self".equals(candidate.getRel())) {
return candidate.getHref();
}
}
throw new RuntimeException("Page does not contain self-reference");
}

public boolean hasChildren() {
return children != null;
}

public List<Link> getLink() {
return link;
}

public void setLink(List<Link> link) {
this.link = link;
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public List<Page> getChildren() {
return children;
}

public void setChildren(List<Page> children) {
this.children = children;
}

public String getBody() {
return body;
}

public void setBody(String body) {
this.body = body;
}

}

public static class Link {

private String rel;
private String href;

public String getRel() {
return rel;
}

public void setRel(String rel) {
this.rel = rel;
}

public String getHref() {
return href;
}

public void setHref(String href) {
this.href = href;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.jbehave.core.io.rest.confluence;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;

import org.jbehave.core.io.rest.RESTClient;
import org.jbehave.core.io.rest.RESTClient.Type;
import org.jbehave.core.io.rest.Resource;
import org.jbehave.core.io.rest.ResourceIndexer;
import org.jbehave.core.io.rest.confluence.Confluence.Page;

public class IndexFromConfluence implements ResourceIndexer {

private static final String DISPLAY = "/display/";

private final Confluence confluence;

public IndexFromConfluence() {
this(null, null);
}

public IndexFromConfluence(String username, String password) {
this(new RESTClient(Type.XML, username, password));
}

public IndexFromConfluence(RESTClient client) {
confluence = new Confluence(client);
}

public Map<String, Resource> indexResources(String rootURI) {
return indexResources(rootURI, null);
}

public Map<String, Resource> indexResources(String rootURI, String rootPath, String syntax, String includes) {
return indexResources(rootURI, includes);
}

protected Map<String, Resource> indexResources(String rootURI, String includePattern) {
if (rootURI == null || !rootURI.contains(DISPLAY)) {
throw new RuntimeException("Root URI is not in correct format: " + rootURI);
}
String[] split = rootURI.split(DISPLAY);
String baseUrl = split[0];
if (split.length == 1) {
throw new RuntimeException("URI does not contain space and page: " + rootURI);
}
String[] searchTerms = split[1].split("/");
if (split.length != 2) {
throw new RuntimeException("URI does not contain space and page: " + rootURI);
}
return createResourceMap(baseUrl, searchTerms[0], searchTerms[1], includePattern);
}

private Map<String, Resource> createResourceMap(String baseUrl, String spaceKey, String pageName, String pattern) {
Map<String, Resource> result = new HashMap<String, Resource>();
Page rootPage = confluence.loadRootPage(baseUrl, spaceKey, pageName);
addPage(result, rootPage.getSelfReference(), pattern);
return result;
}

private void addPage(Map<String, Resource> result, String href, String pattern) {
Page page = confluence.loadPage(href, true);
Resource resource = new Resource(page.getSelfReference(), page.getTitle());
resource.setContent(page.getBody());
if (pattern == null ||
(pattern != null && Pattern.matches(pattern, page.getTitle()))) {
result.put(page.getTitle(), resource);
}
if (page.hasChildren()) {
for (Page child : page.getChildren()) {
addPage(result, child.getSelfReference(), pattern);
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.jbehave.core.io.rest.confluence;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import org.jbehave.core.io.ResourceLoader;
import org.jbehave.core.io.rest.RESTClient;
import org.jbehave.core.io.rest.RESTClient.Type;
import org.jbehave.core.io.rest.confluence.Confluence.Page;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;

public class LoadFromConfluence implements ResourceLoader {

private final Confluence confluence;
private final Set<String> acceptedMacros = new HashSet<String>(Arrays.asList("panel", "info"));

public LoadFromConfluence() {
this(null, null);
}

public LoadFromConfluence(String username, String password) {
this(new RESTClient(Type.XML, username, password));
}

public LoadFromConfluence(RESTClient client) {
confluence = new Confluence(client);
}

public String loadResourceAsText(String resourcePath) {
Page page = confluence.loadPage(resourcePath, false);
Document doc = Jsoup.parse(page.getBody());
StringBuilder builder = new StringBuilder();
addTitle(doc, builder);
addPanels(doc, builder);
addExamples(doc, builder);
return builder.toString();
}

protected void addTitle(Document doc, StringBuilder builder) {
Element titleEl = doc.getElementsByTag("h1").first();
String title = titleEl.text();
builder.append(title).append("\n\n");
}

protected void addPanels(Document doc, StringBuilder builder) {
Elements elements = doc.getElementsByTag("ac:structured-macro");
for (Element element : elements) {
String name = element.attr("ac:name");
if (acceptedMacros.contains(name)) {
appendMacroTitle(builder, element);
appendMacroBody(builder, element);
}
}
}

private void appendMacroTitle(StringBuilder builder, Element element) {
Elements parameters = element.getElementsByTag("ac:parameter");
if (parameters.size() > 0) {
for (Element param : parameters) {
if ("title".equals(param.attr("ac:name"))) {
String text = param.text();
if (!text.contains(":")) {
text = text + ":";
}
builder.append(text).append("\n");
}
}
}
}

private void appendMacroBody(StringBuilder builder, Element element) {
Elements bodies = element.getElementsByTag("ac:rich-text-body");
if (!bodies.isEmpty()) {
Element body = bodies.first();
cleanNodes(body, "div");
cleanNodes(body, "p");
builder.append(body.text().replaceAll("<br/>", "\n")).append("\n");
}
}

protected void addExamples(Document doc, StringBuilder builder) {
Elements tables = doc.getElementsByTag("table");
if (!tables.isEmpty()) {
builder.append("Examples:\n");
Element table = tables.first();
Elements headers = table.select("tr").first().select("th");
for (Element header : headers) {
builder.append("|").append(header.text());
}
builder.append("|\n");
Elements data = table.select("tr");
for (int i = 1; i < data.size(); i++) {
for (Element cell : data.get(i).select("td")) {
builder.append("|").append(cell.text());
}
builder.append("|\n");
}
}
}

protected void cleanNodes(Element element, String tag) {
Elements elementsByTag = element.getElementsByTag(tag);
for (Element el : elementsByTag) {
if (el == null || el.parent() == null) {
continue;
}
Elements children = el.children().select(tag);
for (Element child : children) {
cleanNodes(child, tag);
}
TextNode text = new TextNode(el.text() + "<br/>", "");
el.replaceWith(text);
}
}
}

0 comments on commit 9b36294

Please sign in to comment.