Skip to content

Commit

Permalink
JENKINS-11276: A hack to get Confluence v4.0 markup edits working.
Browse files Browse the repository at this point in the history
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 18d80ef
Show file tree
Hide file tree
Showing 16 changed files with 205 additions and 88 deletions.
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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() {
Expand Down
Expand Up @@ -31,6 +31,7 @@ public class ConfluenceSession {
* Confluence SOAP service
*/
private final ConfluenceSoapService service;
private final jenkins.plugins.confluence.soap.v2.ConfluenceSoapService serviceV2;

/**
* Authentication token, obtained from
Expand All @@ -44,11 +45,14 @@ public class ConfluenceSession {
* 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;
}
Expand Down Expand Up @@ -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
*
Expand Down Expand Up @@ -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);
}
}
Expand Up @@ -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() {
Expand Down Expand Up @@ -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);
Expand Down
Expand Up @@ -15,6 +15,9 @@ public class Util {
/** 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
*
Expand All @@ -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();
}
}
Expand Up @@ -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);
}
}
}
Expand Up @@ -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 {
Expand All @@ -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);
Expand All @@ -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();
}

Expand Down
Expand Up @@ -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 {
Expand All @@ -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();
}

Expand Down
Expand Up @@ -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 {
Expand All @@ -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);
Expand All @@ -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();
}

Expand Down

0 comments on commit 18d80ef

Please sign in to comment.