Codegeist 2011: Scroll Wiki EPUB Exporter is the Best Overall Plugin

Today, the winners of the 2011 Codegeist contest were announced and we won the Best Overall Plugin category - hooray! Congrats to K15t's co-founder Tobias Anstett, who developed the plugin.

The Scroll Wiki EPUB Exporter leverages the Scroll Wiki infrastructure to export single pages or complete pages trees to the EPUB format and download it to your iPad, iPhone or any other EPUB capable format.

The Scroll Wiki EPUB Exporter is currently in beta and will be generally available later this year. If you want to try it out yourself now:

  1. Download a beta from the Atlassian Plugin Exchange
  2. Email epub@k15t.com to get a free 30-day trial license.

Publish Confluence to iPad: The Scroll Wiki EPUB Exporter

Wouldn't it be nice to take the content of your wiki offline on your iPad and read it on the plane? We have news for you...

For the Codegeist Contest by Atlassian, K15t's Tobias Anstett created a new Exporter for Confluence: The Scroll Wiki EPUB Exporter. EPUB is a standard for eBooks, and is used on iPad and iPhones. Just watch this:

If you like, it please don't forget to vote: http://codegeist.atlassian.com/entry/168739

 

Sending Email from Confluence Plugins

Confluence provides a great infrastructure for doing all kinds of things in plugins. One requirement I came across a few times is sending emails from Confluence, which I will explain in this article.

There are two ways of sending emails in Confluence: 1. Synchronous 2. Asynchronous using a mail queue

I will explain option 2, as synchronous sending will make the user wait until the email is sent to the mail server. Asynchronous sending is implemented using a queuing mechanism, and will send the email every 60 seconds. This implementation build on the com.atlassian.core.task.MultiQueueTaskManager which can be injected as a dependency by the Confluence plugin system. After that it is very easy to send emails:

:
String emailText = "Email Text with HTML";
ConfluenceMailQueueItem item = new ConfluenceMailQueueItem(
        contact.getEmail(), 
        null, 
        subject, 
        emailText, 
        ConfluenceMailQueueItem.MIME_TYPE_HTML);
item.setFromName("Example Inc.");
item.setFromAddress("website@example.com");
taskManager.addTask("mail", item);
:

For rendering the email text from templates the VelocityManager is quite handy, which I will explain in another post.

Releasing a Jira Plugin with Maven release:perform

Introduction

As more projects become agile, the amount of releases (deployments) increases excessively. These releases require a lot of manual work that can be automated with Maven. The following article describes how to release Jira plugins using the command mvn release:prepare and mvn release:perform

Prerequisites

You need a svn command line client on your machine. Mac users are happy because their machine comes with one. Windows users have to download one. See http://subversion.apache.org/.

Configure your Maven

Add the following to code to your pom:

:
  <packaging>atlassian-plugin</packaging>
    <scm>
      <connection>scm:svn:https://k15t.jira.com/svn/TEST/trunk</connection>
    </scm>
  <dependencies>
  :

Build your Plugin
  1. Go to your project directory where the pom.xml is located. Execute the following command:
    mvn -Dusername=sebastian.lenk -Dpassword=xxx -DscmCommentPrefix="[TEST-1]MvnRelease" -Dgoals=package release:prepare release:perform
  2. Enter the requested information for release version, release tag and the new development version.
  3. If the build is successful you find in the target folder the jar file.

If the build fails you can roll it back with: mvn -DscmCommentPrefix="[TEST-1]MvnRelease" release:rollback

What maven did for you
  1. updates the version of your project by removing the -SNAPSHOT designation. For example, 1.2-SNAPSHOT will be updated to 1.2.
  2. tags the release in svn as projectname-1.2.
  3. creates the plugin jar in the target folder with the correct version number. For example 1.2
  4. again updates the version of your project to the next SNAPSHOT revision. To continue the example above, the project would now be at revision 1.3-SNAPSHOT.
  5. commits everything to svn.

Document/Literal Web-Services for Confluence

This is the first post from the technical department. In case you have additional questions please contact the Stefan at stefan at k15t dot com. Please add comments at the original post here.

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.