Introduction

Plugins enhance the functionality of Openfire. This document is a developer's guide for creating plugins.

The plugin development is now based on Maven(instead of Ant)

What is Maven

Maven is a project management tool which can manage the complete building life cycle. Maven simplifies and standardizes the project build process. by handling compilation, testing, library dependency, distribution, documentation and team collaboration. The Maven developers claim that Maven is more than just a build tool. We can think of Maven as a build tool with more features. Maven provides developers ways to manage project(Builds,Test,Documentation,Reporting,Dependencies,Releases,Distribution,Mailing List).

Structure of a Plugin

Plugins live in the plugins directory of openfireHome. When a plugin is deployed as a JAR or WAR file, it is automatically expanded into a directory. The files in a plugin directory are as follows:

Plugin Structure
myplugin/
 |- pom.xml         <- Plugin description/configuration file of the Maven project (allows you to define the POM (Project Object Model) used by Maven).
 |- plugin.xml      <- Plugin definition file
 |- readme.html     <- Optional readme file for plugin, which will be displayed to end users
 |- changelog.html  <- Optional changelog file for plugin, which will be displayed to end users
 |- logo_small.gif  <- Optional small (16x16) icon associated with the plugin (can also be a .png file)
 |- logo_large.gif  <- Optional large (32x32) icon associated with the plugin (can also be a .png file)
 |- src   
    |- classes/        <- Resources your plugin needs (i.e., a properties file)
    |- database/       <- Optional database schema files that your plugin needs
    |- i18n/           <- Optional i18n files to allow for internationalization of plugins.
    |- lib/            <- Libraries (JAR files) your plugin needs
    |- java/           <- This is the directory containing the sources of the plugin application(.java files) and located in the package
    |- web             <- Resources for Admin Console integration, if any
        |- WEB-INF/
            |- web.xml           <- Generated web.xml containing compiled JSP entries
            |- web-custom.xml    <- Optional user-defined web.xml for custom servlets
        |- images/

The web directory exists for plugins that need to add content to the Openfire Admin Console. Further details are below.

The plugin.xml file specifies the main Plugin class. A sample file might look like the following:

Sample plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
    <!-- Main plugin class -->
    <class>org.example.ExamplePlugin</class>

    <!-- Plugin meta-data -->
    <name>Example Plugin</name>
    <description>This is an example plugin.</description>
    <author>Jive Software</author>

    <version>1.0</version>
    <date>07/01/2006</date>
    <url>http://www.igniterealtime.org/projects/openfire/plugins.jsp</url>
    <minServerVersion>3.0.0</minServerVersion>
    <licenseType>gpl</licenseType>

    <!-- Admin console entries -->
    <adminconsole>
        <!-- More on this below -->
    </adminconsole>
</plugin>

The meta-data fields that can be set in the plugin.xml file:

Several additional files can be present in the plugin to provide additional information to end-users (all placed in the main plugin directory):

Your plugin class must be implement the Plugin interface from the Openfire API as well as have a default (no argument) contructor. The Plugin interface has methods for initializing and destroying the plugin.

Sample plugin implementation
package org.example;

import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;

import java.io.File;

/**
 * A sample plugin for Openfire.
 */
public class ExamplePlugin implements Plugin {

    public void initializePlugin(PluginManager manager, File pluginDirectory) {
        // Your code goes here

    }

    public void destroyPlugin() {
        // Your code goes here
    }
}

General Plugin Best Practices

When choosing a package name for your plugin, we recommend that you choose something distinctive to you and/or your organization to help avoid conflicts as much as possible. For example, if everyone went with org.example.PluginName, even if PluginName was different, you might start running into some conflicts here and there between class names. This is especially true when working with clustering.

.

Modifying the Admin Console

Plugins can add tabs, sections, and pages to the admin console. There are a several steps to accomplishing this:

The <adminconsole /> section of plugin.xml defines additional tabs, sections and entries in the Admin Console framework. A sample plugin.xml file might look like the following:

Sample plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
    <!-- Main plugin class -->
    <class>org.example.ExamplePlugin</class>

    <!-- Admin console entries -->

    <adminconsole>
        <tab id="mytab" name="Example" url="my-plugin-admin.jsp" description="Click to manage...">
            <sidebar id="mysidebar" name="My Plugin">
               <item id="my-plugin" name="My Plugin Admin"
                   url="my-plugin-admin.jsp"
                   description="Click to administer settings for my plugin"
                   order="4" />
               <item id="my-plugin" name="My Plugin Overview"
                   url="my-plugin-overview.jsp"
                   description="Click to have an Overview of Plugin usage"
                   order="2" />
            </sidebar>
        </tab>

    </adminconsole>
</plugin>

In this example, we've defined a new tab "Example", a sidebar section "My Plugin" and two pages: "My Plugin Admin" and "My Plugin Overview". We've registered my-plugin-admin.jsp respectively my-plugin-overview.jsp as the pages.

By default, the tabs, sidebars and pages will be presented in the order in which they are defined. You can, however, define explicit ordering by adding an "order" attribute to each element. It's numeric value defines order. If no order is specified, the value 0 (zero) is used as a default. In the example above, the items are ordered using this construct. In the admin console, the "My Plugin Overview" page will be presented before the "My Plugin Admin" page, as its 'order' value is lower. If neither item had defined the 'order' attribute, the presentation of both pages would have been reversed (as it would have used to order in which the pages are defined in XML).

You can override existing tabs, sections, and items by using the existing id attribute values in your own <adminconsole> definition.

Admin Console Best Practices

There are several best practices to consider when making changes to the Openfire admin console via a plugin. The general theme is that plugins should integrate seamlessly:

Writing Pages for the Admin Console

Openfire uses the Sitemesh framework to decorate pages in the admin console. A globally-defined decorator is applied to each page in order to render the final output, as in the following diagram:


Sitemesh


Creating pages that work with Sitemesh is easy. Simply create valid HTML pages and then use meta tags to send instructions to Sitemesh. When rendering the output, Sitemesh will use the instructions you provide to render the decorator along with any content in the body of your HTML page. The following meta tags can be used:

The following HTML snippet demonstrates a valid page:
Sample HTML
   <html>
   <head>
       <title>My Plugin Page</title>

       <meta name="pageID" content="myPluginPage"/>
   </head>
   <body>
        Body here!
   </body>
   </html>

Using i18n in your Plugins

It's possible to translate your plugin into multiple languages (i18n). To do so, use the following procedure:

Using the Openfire Build Script

The Openfire build script will help you build and develop plugins. It looks for plugin development directories in the following format:

Plugin Structure
myplugin/
 |- plugin.xml      <- Plugin definition file
 |- readme.html     <- Optional readme file for plugin
 |- changelog.html  <- Optional changelog file for plugin
 |- logo_small.gif  <- Optional small (16x16) icon associated with the plugin (can also be a .png file)
 |- logo_large.gif  <- Optional large (32x32) icon associated with the plugin (can also be a .png file)
 |- classes/        <- Resources your plugin needs (i.e., a properties file)
 |- lib/            <- Libraries your plugin needs
 |- src/
     |- database    <- Optional database scripts for your plugin
     |- java        <- Java source code for your plugin
     |   |- com
     |       |- mycompany
     |           |- *.java
     |- web
         |- *.jsp      <- JSPs your plugin uses for the admin console
         |- images/    <- Any images your JSP pages need (optional)
         |- WEB-INF
             |- web.xml    <- Optional file where custom servlets can be registered

The build script will compile source files and JSPs and create a valid plugin structure and JAR file. Put your plugin directories in the src/plugins directory of the source distribution and then use ant plugins to build your plugins.

Any JAR files your plugin needs during compilation should be put into the lib directory. These JAR files will also be copied into the plugin's generated lib directory as part of the build process.

If you create a src/web/WEB-INF/web.xml file, any servlets registered there will be initialized when the plugin starts up. Only servlet registrations and servlet mappings will be honored from the web.xml file. Note: this feature is implemented by merging your custom web.xml file into the web.xml file generated by the JSP compilation process.

Implementing Your Plugin

Plugins have full access to the Openfire API. This provides a tremendous amount of flexibility for what plugins can accomplish. However, there are several integration points that are the most common:

  1. Register a plugin as a Component. Components receive all packets addressed to a particular sub-domain. For example, test_component.example.com. So, a packet sent to joe@test_component.example.com would be delivered to the component. Note that the sub-domains defined as components are unrelated to DNS entries for sub-domains. All XMPP routing at the socket level is done using the primary server domain (example.com in the example above); sub-domains are only used for routing within the XMPP server.
  2. Register a plugin as an IQHandler. IQ handlers respond to IQ packets with a particular element name and namespace. The following code snippet demonstrates how to register an IQHandler:
    
      IQHandler myHandler = new MyIQHander();
      IQRouter iqRouter = XMPPServer.getInstance().getIQRouter();
      iqRouter.addHandler(myHandler);
      
  3. Register a plugin as a PacketInterceptor to receive all packets being sent through the system and optionally reject them. For example, an interceptor could reject all messages that contained profanity or flag them for review by an administrator.
  4. You can store persistent plugin settings as Openfire properties using the JiveGlobals.getProperty(String) and JiveGlobals.setProperty(String, String) methods. Make your plugin a property listener to listen for changes to its properties by implementing the org.jivesoftware.util.PropertyEventListener method. You can register your plugin as a listener using the PropertyEventDispatcher.addListener(PropertyEventListener) method. Be sure to unregister your plugin as a listener in your plugin's destroyPlugin() method.

Openfire admin tags

Openfire provides useful JSP tags that can be used. To enable them on a JSP page, simply add:
<%@ taglib uri="admin" prefix="admin" %>
to the top of your JSP page. The tags include:

CSRF protection

Admin pages are liable to CSRF attacks. Openfire provides facilities to aid plugin authors to protect against these attacks on their admin pages. To enable CSRF protection:
  1. Set the plugin.xml minServerVersion to 4.5.0 or above as this is when support was added.
  2. Set the plugin.xml csrfProtectionEnabled to true to enable CSRF protection for the plugin. This will;
    • Guard against CSRF attacks for all requests to admin pages except GET requests
    • Set a servlet request attribute with key "csrf"
  3. Ensure that GET requests do not modify any settings or change any data as this protection is not enabled for GET requests
  4. Ensure that any form submitted in the admin page has a field called csrf whose value is that defined by the request attribute "csrf" - for example:
    <input name="csrf" value="<c:out value="${csrf}"/>" type="hidden">
If a CSRF attack is detected, the admin page will be reloaded (with a simple HTTP GET request) with the session attribute FlashMessageTag.ERROR_MESSAGE_KEY set to indicate the problem - it's therefore advised to include the <admin:FlashMessage/> at the top of your JSP page.

NOTE: It is still important to ensure that all your output is properly escaped using <c:out> tags or the equivalent.

Plugin FAQ

Can I deploy a plugin as a directory instead of a JAR?

No, all plugins must be deployed as JAR or WAR files. When a JAR or WAR is not present for the plugin, Openfire assumes that the file has been deleted and that the users wants to destroy the plugin, so it also deletes the directory.