Skip to content
Permalink
Browse files

JENKINS-11276: A hack to get Confluence v4.0 markup edits working.

In Confluence 4.0, you have to use the v2 SOAP endpoint in order
to fetch the page content, and then the content can only be
edited in an XML format.

The plugin is not very intelligent at the moment, as far as
understanding the new <ac:macro/>-style XML tags, and being able
to simplify the token filtering process. Instead, you'll need
to enter the tokens in their X(HT)ML format, which can be found
by going to Tools > View Storage Format from a Confluence page.
Also note that these tokens can simply be visible tokens within
the markup.
  • Loading branch information
Joe Hansche committed Oct 18, 2011
1 parent 1e4ad9b commit 18d80ef7a29e8f3a79d3c7e15f93a1c2105e2257
Showing with 205 additions and 88 deletions.
  1. +57 −23 src/main/java/com/myyearbook/hudson/plugins/confluence/ConfluencePublisher.java
  2. +21 −1 src/main/java/com/myyearbook/hudson/plugins/confluence/ConfluenceSession.java
  3. +13 −4 src/main/java/com/myyearbook/hudson/plugins/confluence/ConfluenceSite.java
  4. +8 −0 src/main/java/com/myyearbook/hudson/plugins/confluence/Util.java
  5. +14 −5 src/main/java/com/myyearbook/hudson/plugins/confluence/rpc/XmlRpcClient.java
  6. +9 −4 src/main/java/com/myyearbook/hudson/plugins/confluence/wiki/editors/AfterTokenEditor.java
  7. +9 −3 src/main/java/com/myyearbook/hudson/plugins/confluence/wiki/editors/AppendEditor.java
  8. +9 −4 src/main/java/com/myyearbook/hudson/plugins/confluence/wiki/editors/BeforeTokenEditor.java
  9. +10 −7 src/main/java/com/myyearbook/hudson/plugins/confluence/wiki/editors/BetweenTokensEditor.java
  10. +2 −1 src/main/java/com/myyearbook/hudson/plugins/confluence/wiki/editors/EntirePageEditor.java
  11. +27 −15 src/main/java/com/myyearbook/hudson/plugins/confluence/wiki/editors/MarkupEditor.java
  12. +10 −3 src/main/java/com/myyearbook/hudson/plugins/confluence/wiki/editors/PrependEditor.java
  13. +6 −6 src/main/java/com/myyearbook/hudson/plugins/confluence/wiki/generators/FileGenerator.java
  14. +8 −7 src/main/java/com/myyearbook/hudson/plugins/confluence/wiki/generators/MarkupGenerator.java
  15. +2 −2 src/main/java/com/myyearbook/hudson/plugins/confluence/wiki/generators/PlainTextGenerator.java
  16. +0 −3 src/main/resources/com/myyearbook/hudson/plugins/confluence/ConfluencePublisher/help-editorList.html
@@ -34,7 +34,6 @@
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import jenkins.plugins.confluence.soap.v1.RemoteAttachment;
import jenkins.plugins.confluence.soap.v1.RemotePage;
@@ -265,26 +264,59 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
}

// Wiki editing is only supported in versions prior to 4.0
if (!confluence.isVersion4() && pageData instanceof RemotePage) {
// Perform wiki replacements
try {
result &= this.performWikiReplacements(build, launcher, listener, confluence,
(RemotePage) pageData);
} catch (IOException e) {
e.printStackTrace(listener.getLogger());
} catch (InterruptedException e) {
e.printStackTrace(listener.getLogger());
if (!editors.isEmpty()) {
if (!confluence.isVersion4() && pageData instanceof RemotePage) {
// Perform wiki replacements
try {
result &= this.performWikiReplacements(build, launcher, listener, confluence,
(RemotePage) pageData);
} catch (IOException e) {
e.printStackTrace(listener.getLogger());
} catch (InterruptedException e) {
e.printStackTrace(listener.getLogger());
}
} else {
log(listener, "EXPERIMENTAL: performing storage format edits on Confluence 4.0");

// Must use the v2 API for this.
jenkins.plugins.confluence.soap.v2.RemotePage pageDataV2 = confluence
.getPageV2(pageData.getId());

try {
result &= this.performWikiReplacements(build, launcher, listener, confluence,
pageDataV2);
} catch (IOException e) {
e.printStackTrace(listener.getLogger());
} catch (InterruptedException e) {
e.printStackTrace(listener.getLogger());
}
}
} else if (!editors.isEmpty()) {
log(listener, "Confluence version 4.0 has moved to a new page storage format.");
log(listener, "Not performing page edits!");
}

// Not returning `result`, because this publisher should not
// fail the job
return true;
}

private boolean performWikiReplacements(AbstractBuild<?, ?> build, Launcher launcher,
BuildListener listener, ConfluenceSession confluence,
jenkins.plugins.confluence.soap.v2.RemotePage pageDataV2) throws IOException,
InterruptedException {

final String editComment = build.getEnvironment(listener).expand(
"Published from Jenkins build: $BUILD_URL");
final jenkins.plugins.confluence.soap.v2.RemotePageUpdateOptions options = new jenkins.plugins.confluence.soap.v2.RemotePageUpdateOptions(
false, editComment);

// Get current content
String content = performEdits(build, listener, pageDataV2.getContent(), true);

// Now set the replacement content
pageDataV2.setContent(content);
confluence.updatePageV2(pageDataV2, options);
return true;
}

/**
* @param build
* @param launcher
@@ -304,23 +336,27 @@ protected boolean performWikiReplacements(AbstractBuild<?, ?> build, Launcher la
final RemotePageUpdateOptions options = new RemotePageUpdateOptions(false, editComment);

// Get current content
String content = pageData.getContent();
String content = performEdits(build, listener, pageData.getContent(), false);

// Now set the replacement content
pageData.setContent(content);
confluence.updatePage(pageData, options);
return true;
}

private String performEdits(final AbstractBuild<?, ?> build, final BuildListener listener,
String content, final boolean isNewFormat) {
for (MarkupEditor editor : this.editors) {
log(listener, "Performing wiki edits: " + editor.getDescriptor().getDisplayName());

try {
content = editor.performReplacement(build, listener, content);
content = editor.performReplacement(build, listener, content, isNewFormat);
} catch (TokenNotFoundException e) {
log(listener, "ERROR while performing replacement: " + e.getMessage());
}
}

// Now set the replacement content
pageData.setContent(content);

confluence.updatePage(pageData, options);

return true;
return content;
}

/**
@@ -363,8 +399,6 @@ public void save() throws IOException {

@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> {
private static final Logger LOGGER = Logger.getLogger(DescriptorImpl.class.getName());

private final List<ConfluenceSite> sites = new ArrayList<ConfluenceSite>();

public DescriptorImpl() {
@@ -31,6 +31,7 @@
* Confluence SOAP service
*/
private final ConfluenceSoapService service;
private final jenkins.plugins.confluence.soap.v2.ConfluenceSoapService serviceV2;

/**
* Authentication token, obtained from
@@ -44,11 +45,14 @@
* Constructor
*
* @param service
* @param serviceV2
* @param token
*/
/* package */ConfluenceSession(final ConfluenceSoapService service, final String token,
/* package */ConfluenceSession(final ConfluenceSoapService service,
jenkins.plugins.confluence.soap.v2.ConfluenceSoapService serviceV2, final String token,
final RemoteServerInfo info) {
this.service = service;
this.serviceV2 = serviceV2;
this.token = token;
this.serverInfo = info;
}
@@ -128,6 +132,13 @@ public RemotePage updatePage(final RemotePage page, final RemotePageUpdateOption
return this.service.updatePage(this.token, page, options);
}

public jenkins.plugins.confluence.soap.v2.RemotePage updatePageV2(
jenkins.plugins.confluence.soap.v2.RemotePage pageDataV2,
jenkins.plugins.confluence.soap.v2.RemotePageUpdateOptions options)
throws RemoteException {
return this.serviceV2.updatePage(token, pageDataV2, options);
}

/**
* Get all attachments for a page
*
@@ -236,4 +247,13 @@ public static String sanitizeFileName(String fileName) {
public boolean isVersion4() {
return this.serverInfo.getMajorVersion() >= 4;
}

public void doV4Test(long id) throws RemoteException {
jenkins.plugins.confluence.soap.v2.RemotePage page = this.serviceV2.getPage(token, id);
System.out.println("Content: " + page.getContent());
}

public jenkins.plugins.confluence.soap.v2.RemotePage getPageV2(long id) throws RemoteException {
return this.serviceV2.getPage(token, id);
}
}
@@ -93,7 +93,15 @@ public ConfluenceSession createSession() throws RemoteException {
}

RemoteServerInfo info = service.getServerInfo(token);
return new ConfluenceSession(service, token, info);

jenkins.plugins.confluence.soap.v2.ConfluenceSoapService serviceV2 = null;

if (info.getMajorVersion() >= 4) {
String v2Url = Util.confluenceUrlToSoapV2Url(url.toExternalForm());
serviceV2 = XmlRpcClient.getV2Instance(v2Url);
}

return new ConfluenceSession(service, serviceV2, token, info);
}

public DescriptorImpl getDescriptor() {
@@ -170,10 +178,11 @@ protected FormValidation check() throws IOException, ServletException {
}

try {
if (findText(open(new URL(newurl)), "Atlassian Confluence"))
if (findText(open(new URL(newurl)), "Atlassian Confluence")) {
return FormValidation.ok();
else
return FormValidation.error("Not a Confluence URL");
}

return FormValidation.error("Not a Confluence URL");
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Unable to connect to " + url, e);
return handleIOException(url, e);
@@ -15,6 +15,9 @@
/** Relative path to resolve the SOAP endpoint URL */
private static final String SOAP_URL_PATH = "rpc/soap-axis/confluenceservice-v1";

/** Relative path to resolve the SOAP v2 endpoint URL */
private static final String SOAP_V2_URL_PATH = "rpc/soap-axis/confluenceservice-v2";

/**
* Convert a generic Confluence URL into the XmlRpc endpoint URL
*
@@ -38,4 +41,9 @@ public static String confluenceUrlToSoapUrl(String url) {
URI uri = URI.create(url);
return uri.resolve(SOAP_URL_PATH).normalize().toString();
}

public static String confluenceUrlToSoapV2Url(String url) {
URI uri = URI.create(url);
return uri.resolve(SOAP_V2_URL_PATH).normalize().toString();
}
}
@@ -5,20 +5,29 @@

import javax.xml.rpc.ServiceException;

import jenkins.plugins.confluence.soap.v1.ConfluenceSoapService;
import jenkins.plugins.confluence.soap.v1.ConfluenceSoapServiceServiceLocator;

public class XmlRpcClient {
protected XmlRpcClient(String url) {
}

public static ConfluenceSoapService getInstance(String url) throws RemoteException {
public static jenkins.plugins.confluence.soap.v1.ConfluenceSoapService getInstance(String url)
throws RemoteException {
try {
final ConfluenceSoapServiceServiceLocator locator = new ConfluenceSoapServiceServiceLocator();
final jenkins.plugins.confluence.soap.v1.ConfluenceSoapServiceServiceLocator locator = new jenkins.plugins.confluence.soap.v1.ConfluenceSoapServiceServiceLocator();
locator.setConfluenceserviceV1EndpointAddress(url);
return locator.getConfluenceserviceV1();
} catch (ServiceException e) {
throw new RemoteException("Failed to create SOAP Client", e);
}
}

public static jenkins.plugins.confluence.soap.v2.ConfluenceSoapService getV2Instance(String url)
throws RemoteException {
try {
final jenkins.plugins.confluence.soap.v2.ConfluenceSoapServiceServiceLocator locator = new jenkins.plugins.confluence.soap.v2.ConfluenceSoapServiceServiceLocator();
locator.setConfluenceserviceV2EndpointAddress(url);
return locator.getConfluenceserviceV2();
} catch (ServiceException e) {
throw new RemoteException("Failed to create SOAP Client", e);
}
}
}
@@ -12,7 +12,7 @@
/**
* Represents a token-based Wiki markup editor that inserts the new content
* immediately following the replacement marker token.
*
*
* @author Joe Hansche <jhansche@myyearbook.com>
*/
public class AfterTokenEditor extends MarkupEditor {
@@ -26,8 +26,8 @@ public AfterTokenEditor(final MarkupGenerator generator, final String markerToke
}

@Override
public String performEdits(BuildListener listener, String content, String generated)
throws TokenNotFoundException {
public String performEdits(final BuildListener listener, final String content,
final String generated, final boolean isNewFormat) throws TokenNotFoundException {
final StringBuffer sb = new StringBuffer(content);

final int start = content.indexOf(markerToken);
@@ -41,7 +41,12 @@ public String performEdits(BuildListener listener, String content, String genera

// Insert the newline at the end of the token, then {generated}
// immediately after that
sb.insert(end, '\n').insert(end + 1, generated);

if (isNewFormat) {
sb.insert(end, generated);
} else {
sb.insert(end, '\n').insert(end + 1, generated);
}
return sb.toString();
}

@@ -11,7 +11,7 @@
/**
* Represents a simple Wiki markup editor that appends the content to the end of
* the page. This editor requires no replacement tokens.
*
*
* @author Joe Hansche <jhansche@myyearbook.com>
*/
public class AppendEditor extends MarkupEditor {
@@ -21,10 +21,16 @@ public AppendEditor(MarkupGenerator generator) {
}

@Override
public String performEdits(BuildListener listener, String content, String generated) {
public String performEdits(final BuildListener listener, final String content,
final String generated, final boolean isNewFormat) {
final StringBuilder sb = new StringBuilder(content);
// Append the generated content to the end of the page
sb.append('\n').append(generated);

if (isNewFormat) {
sb.append(generated);
} else {
sb.append('\n').append(generated);
}
return sb.toString();
}

@@ -12,7 +12,7 @@
/**
* Represents a token-based Wiki markup editor that inserts the new content
* immediately before the replacement marker token.
*
*
* @author Joe Hansche <jhansche@myyearbook.com>
*/
public class BeforeTokenEditor extends MarkupEditor {
@@ -26,8 +26,8 @@ public BeforeTokenEditor(final MarkupGenerator generator, final String markerTok
}

@Override
public String performEdits(BuildListener listener, String content, String generated)
throws TokenNotFoundException {
public String performEdits(final BuildListener listener, final String content,
final String generated, final boolean isNewFormat) throws TokenNotFoundException {
final StringBuffer sb = new StringBuffer(content);

final int start = content.indexOf(markerToken);
@@ -39,7 +39,12 @@ public String performEdits(BuildListener listener, String content, String genera

// Insert the newline at {start} first, and then {generated}
// (the newline will appear after {generated})
sb.insert(start, '\n').insert(start, generated);

if (isNewFormat) {
sb.insert(start, generated);
} else {
sb.insert(start, '\n').insert(start, generated);
}
return sb.toString();
}

0 comments on commit 18d80ef

Please sign in to comment.