Document/Literal Web-Services for Confluence

Filed under: Java

In this post I describe how to create doc/literal-style Web Services for Confluence using XFire.

Why do you want to do that?

There are two alternatives. Confluence provides the web service plugin module, which offer good support for rpc-style services. However, these don’t work well with some clients, as some platforms do not support rpc-style web services.

Instead of using web services REST might be an alternative. Since Confluence 3.1. there is a plugin module type for REST interfaces, which builds the JAX-WS reference implementation called Jersey. Btw, the REST plugin module type also work on Confluence 3.0, but you will have to install some plugins manually. While REST is certainly the way to go for future developments, it has the same short coming as RPC-style web services: client platforms may not be able to use it (or at least make it hard to use it).

In my case the doc-literal web service client is InfoPath (part of Microsoft Office), which works allows users to edit pages in a office-style application and better keeps the structure of a page than the Word editor.

Overview

XFire is a quite old project and is actually now maintained/developed as CXF at Apache. However, there are three reasons for using XFire: XFire is included in the Confluence distribution, CXF still has some dependencies to XFire and introducing another library doesn’t make things easier (believe me I tried).

In order to use XFire we need to setup the dependencies correctly, create and deploy a XFireServlet as a servlet module, which handles the HTTP request and invokes the XFire web service stack.

POM Configuration

It is not possible to import the XFire packages through OSGi Bundle-Instructions (Atlassian does not expose it to plugins for whatever reason), so it is necessary define XFire as a dependencies. On the other hand it is necessary to exclude all unneeded or conflicting dependendencies of XFire. I added the following dependencies to the default dependency section:

<dependency>
    <groupId>org.codehaus.xfire</groupId>
    <artifactId>xfire-aegis</artifactId>
    <version>1.2.6</version>
</dependency>
<dependency>
    <groupId>org.codehaus.xfire</groupId>
    <artifactId>xfire-java5</artifactId>
    <version>1.2.6</version>
    <exclusions>
        <exclusion>
            <groupId>org.codehaus.xfire</groupId>
            <artifactId>xfire-annotations</artifactId>
        </exclusion>
        <exclusion>
            <groupId>xfire</groupId>
            <artifactId>xfire-jsr181-api</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.codehaus.xfire</groupId>
    <artifactId>xfire-core</artifactId>
    <version>1.2.6</version>
    <exclusions>
        <exclusion>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
        </exclusion>
        <exclusion>
            <groupId>javax.mail</groupId>
            <artifactId>mail</artifactId>
        </exclusion>
        <exclusion>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
        </exclusion>
        <exclusion>
            <groupId>stax</groupId>
            <artifactId>stax-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.apache.ws.commons</groupId>
            <artifactId>XmlSchema</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.codehaus.woodstox</groupId>
            <artifactId>wstx-asl</artifactId>
        </exclusion>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
        <exclusion>
            <groupId>commons-httpclient</groupId>
            <artifactId>commons-httpclient</artifactId>
        </exclusion>
    </exclusions>
</dependency>

The XFire-Servlet

The XFire-Servlet handles HTTP Requests and invokes the XFire web service stack, which in turn hands over the web service call to our service implementation. Therefore the servlet initializes the service in the servlets init() method (when trying this yourself, please not that the init() method gets called lazily on first access).

public class MyXfireServlet extends XFireServlet {

    public void init() throws ServletException {
        super.init();

        // The ObjectServiceFactory creates services from Java objects
        ObjectServiceFactory factory = new ObjectServiceFactory(getXFire().getTransportManager(), null);

        // we don't want to expose compareTo
        factory.addIgnoredMethods("java.lang.Comparable");
        Service service = factory.create(DokumentService.class, "dokument", null, null);
        service.setProperty(ObjectInvoker.SERVICE_IMPL_CLASS, DokumentServiceImpl.class);

        // unregister old Service
        Service oldService = getServiceRegistry().getService("dokument");
        if(oldService != null) {
            getServiceRegistry().unregister(oldService);
        }

        getServiceRegistry().register(service);
    }

    protected ServiceRegistry getServiceRegistry() throws ServletException {
        return getController().getServiceRegistry();
    }
}

To register the service the following is needed in Atlassian XML:

<servlet name="Form Editor Servlet" key="com.k15t.example"
    class="com.k15t.example.webservice.XfireServlet">
  <url-pattern>/xfire/*</url-pattern>
</servlet>

This will make the XfireServlet to be available at http://localhost:1990/confluence/plugins/servlet/xfire/ (replace the hostname, port and context root and note the trailing slash). It will list the available services including a link to retrieve the WSDL for the service. In our case there is one service called “dokument”.

Implementing the Service

As you can see above the service is defined in the interface DokumentService and the implementation is implemented in DokumentServiceImpl. XFire will take care of converting the SOAP message to method parameters, if the service interface only uses the following types (source):

  • Basic types: int, double, float, long, byte[], short, String, BigDecimal
  • Arrays
  • Collections
  • Dates: java.util.Date, java.util.Calendar, java.sql.Timestamp, java.sql.Date, java.sql.Time
  • XML: org.w3c.dom.Docmument, org.jdom.Element, XMLStreamReader, Source
  • Complex types which are aggregations of the above

This is the interface of the service, the implementation is calling the Confluence API to do stuff (Dokument is a complex bean containing some page data):

public interface DokumentService {

    public Dokument load(String spaceName, String pageId, String token);

    public void save(Dokument document, String token);

}

That’s all.

It is important to notice, that the ServiceRegistry is shared between different XFireServlets, so take care if you install multiple of those.

Jan 12, 2010 at 12:06 | Permalink

3 Comments

  • 1. Steve Androulakis  |  April 22nd, 2010 at 12:31 pm

    I created an Xfire service that worked outside of confluence. Now when putting it in my confluence plugin and using your guide to maven dependencies, it builds. When I try and run it however, it crashes when trying to make the first call (initialisation):

    Nested exception is java.lang.ClassCastException: org.codehaus.xfire.aegis.AegisBindingProvider

    AegisBindingProvider, in that package is definitely in my dependency list.. some kind of conflict with the inbuilt one preventing it from being cast properly?

    P.S. I’m not using a servlet. I’m just calling my generated stubs from execute() in the plugin. Maybe I need to put something in the atlassian config?

    So confused! Help is much appreciated :)

  • 2. Stefan Kleineikenscheidt  |  April 22nd, 2010 at 8:49 pm

    That’s probably not easy to fix. It looks like you are having the AegisBindingProvider twice on your classpath (in Confluence and in your plugin), and the wrong one gets initialized. Debugging this is really a pain, I think it’s best you post the full stacktrace to the atlassian dev forum.

  • 3. Steve Androulakis  |  April 23rd, 2010 at 12:27 am

    Cheers Stefan, I’ll make a post there.

Leave a Comment

(Required, Why?)

(Required)

(Optional)

Some HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

Trackback this post  |  Subscribe to the comments via RSS Feed