Tuesday January 12, 2010
Document/Literal Web-Services for Confluence
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.
Posted on Jan 12, 2010 at 12:06 (MET) | Permalink | 3 comments