plugin-dev-guide.html 19.6 KB
Newer Older
Matt Tucker's avatar
Matt Tucker committed
1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2

Matt Tucker's avatar
Matt Tucker committed
3 4
<html>
<head>
5
<title>Openfire: Plugin Developer Guide</title>
6
<link href="style.css" rel="stylesheet" type="text/css">
Matt Tucker's avatar
Matt Tucker committed
7 8 9
</head>
<body>

10
<div id="pageContainer">
Matt Tucker's avatar
Matt Tucker committed
11

12 13
<a name="top"></a>

14 15
	<div id="pageHeader">
		<div id="logo"></div>
16
		<h1>Openfire Plugin Developer Guide</h1>
17 18 19 20 21 22 23 24
	</div>
	<div class="navigation">
		<a href="index.html">&laquo; Back to documentation index</a>
	</div>

	<div id="pageBody">


Matt Tucker's avatar
Matt Tucker committed
25 26 27
<h2>Introduction</h2>

<p>
28
Plugins enhance the functionality of Openfire. This document is a
29 30
developer's guide for creating plugins.
</p>
Matt Tucker's avatar
Matt Tucker committed
31 32 33

<h2>Structure of a Plugin</h2>

34
<p>
35
Plugins live in the <tt>plugins</tt> directory of <tt>openfireHome</tt>. When a plugin
Matt Tucker's avatar
Matt Tucker committed
36
is deployed as a JAR or WAR file, it is automatically expanded into a directory. The files in a
37
plugin directory are as follows:
38

39 40 41 42 43
</p>

<fieldset>
    <legend>Plugin Structure</legend>
<pre>myplugin/
44 45 46
 |- plugin.xml      &lt;- Plugin definition file
 |- readme.html     &lt;- Optional readme file for plugin, which will be displayed to end users
 |- changelog.html  &lt;- Optional changelog file for plugin, which will be displayed to end users
Matt Tucker's avatar
Matt Tucker committed
47 48
 |- logo_small.gif  &lt;- Optional small (16x16) icon associated with the plugin (can also be a .png file)
 |- logo_large.gif  &lt;- Optional large (32x32) icon associated with the plugin (can also be a .png file)
49
 |- classes/        &lt;- Resources your plugin needs (i.e., a properties file)
50 51
 |- database/       &lt;- Optional database schema files that your plugin needs
 |- i18n/           &lt;- Optional i18n files to allow for internationalization of plugins.
52 53
 |- lib/            &lt;- Libraries (JAR files) your plugin needs
 |- web             &lt;- Resources for Admin Console integration, if any
Matt Tucker's avatar
Matt Tucker committed
54
     |- WEB-INF/
55 56
         |- web.xml           &lt;- Generated web.xml containing compiled JSP entries
         |- web-custom.xml    &lt;- Optional user-defined web.xml for custom servlets
Matt Tucker's avatar
Matt Tucker committed
57
     |- images/
58

59 60 61
</pre>
</fieldset>

Matt Tucker's avatar
Matt Tucker committed
62
<p>The <tt>web</tt> directory exists for plugins that need to add content
63
to the Openfire Admin Console. Further details are below.</p>
Matt Tucker's avatar
Matt Tucker committed
64

65
<p>
Matt Tucker's avatar
Matt Tucker committed
66 67
The <tt>plugin.xml</tt> file specifies the main Plugin class. A sample
file might look like the following:
68
</p>
Matt Tucker's avatar
Matt Tucker committed
69

70
<fieldset>
71

72 73
    <legend>Sample plugin.xml</legend>
<pre class="xml">
Matt Tucker's avatar
Matt Tucker committed
74 75
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;plugin&gt;
76
    <span class="comment">&lt;!-- Main plugin class --&gt;</span>
Matt Tucker's avatar
Matt Tucker committed
77
    &lt;class&gt;org.example.ExamplePlugin&lt;/class&gt;
Matt Tucker's avatar
Matt Tucker committed
78

79 80 81 82
    <span class="comment">&lt;!-- Plugin meta-data --&gt;</span>
    &lt;name&gt;Example Plugin&lt;/name&gt;
    &lt;description&gt;This is an example plugin.&lt;/description&gt;
    &lt;author&gt;Jive Software&lt;/author&gt;
83

84
    &lt;version&gt;1.0&lt;/version&gt;
85
    &lt;date&gt;07/01/2006&lt;/date&gt;
Gaston Dombiak's avatar
Gaston Dombiak committed
86
    &lt;url&gt;http://www.igniterealtime.org/projects/openfire/plugins.jsp&lt;/url&gt;
87 88
    &lt;minServerVersion&gt;3.0.0&lt;/minServerVersion&gt;
    &lt;licenseType&gt;gpl&lt;/licenseType&gt;
Matt Tucker's avatar
Matt Tucker committed
89

90 91 92 93
    <span class="comment">&lt;!-- Admin console entries --&gt;</span>
    &lt;adminconsole&gt;
        <span class="comment">&lt;!-- More on this below --&gt;</span>
    &lt;/adminconsole&gt;
Matt Tucker's avatar
Matt Tucker committed
94 95
&lt;/plugin&gt;
</pre>
96 97
</fieldset>

98 99
<p>The meta-data fields that can be set in the plugin.xml file:

Matt Tucker's avatar
Matt Tucker committed
100
<ul>
101 102 103
    <li>name -- the name of the plugin.</li>
    <li>description -- the description of the plugin.</li>
    <li>author -- the author of the plugin.</li>
104
    <li>version -- the version of the plugin.</li>
105
    <li>date -- the date the plugin was released. The date must be in the form MM/dd/yyyy, such
106
          as 07/01/2006.</li>
107

108
    <li>url -- a URL where additional information about the plugin is available.</li>
109 110
    <li>minServerVersion -- the minimum version of Openfire required
          to run the plugin (supported by Openfire 2.1.2 and later). If the
111
          server version is less than the required value, the plugin will not be started.</li>
112 113 114 115
    <li>databaseKey -- if the plugin requires it's own database tables, the databaseKey element should
            be set with a schema key name (often the same name as the plugin). Database
            schema files for each supported database should then be placed in the <tt>database</tt>
            directory of the plugin. For example, given the key "foo", schema files would be called
116 117 118 119
            "foo_mysql.sql", "foo_oracle.sql", etc.  We recommend that you prefix your tables with
            "of" (openfire) to avoid conflicts with possible other applications installed in the same
            database.  The scripts should make an entry into the ofVersion table using the key so that
            schema version information can be tracked, e.g.:<br><br>
120

121
            <tt>INSERT INTO ofVersion (name, version) VALUES ('foo', 0);</tt><br><br>
122 123 124 125 126 127 128
    </li>
    <li>databaseVersion -- the database schema version (if a database schema is defined). New plugins
            with a database schema should start at version 0. If future versions of the plugin
            require updates to the schema, those updates can be defined by creating sub-directories
            in the <tt>database/upgrade</tt> directory for each version number. For example, the directories
            <tt>database/upgrade/1</tt> and <tt>database/upgrade/2</tt> would contain scripts such as
            "foo_mysql.sql" and "foo_oracle.sql" that contain the relevant database changes for each
129
            version. Each script should update version information in the ofVersion table, e.g.:<br><br>
130

131
            <tt>UPDATE ofVersion set version=1 where name='foo';</tt><br><br>
132 133

    </li>
134
    <li>parentPlugin -- the name of the parent plugin (given as "foo" for the "foo.jar" plugin).
Matt Tucker's avatar
Matt Tucker committed
135 136
            When a plugin has a parent plugin, the parent plugin's class loader will be used instead
            of creating a new class loader. This lets plugins work together more closely. A
137
            child plugin will not function without its parent present.</li>
138 139 140 141 142 143 144 145 146 147 148 149
    <li>licenseType -- indicates the license agreement that the plugin is governed by. Valid
            values are:<ul>
                <li>"commercial": the plugin is released under a commercial license agreement.</li>
                <li>"gpl": the plugin is released under the GNU Public License (GPL).</li>
                <li>"apache": the plugin is released under the Apache license.</li>
                <li>"internal": the plugin is for internal use at an organization only and will
                    not be re-distributed.</li>
                <li>"other": the plugin is released under a license agrement that doesn't fall into
                    one of the other categories. The license agreement should be details in the
                    plugin's Readme.</li>
            </ul>
            If the license type is not set, it is assumed to be other.</li>
Matt Tucker's avatar
Matt Tucker committed
150 151
</ul></p>

152 153 154 155
Several additional files can be present in the plugin to provide additional information to
end-users (all placed in the main plugin directory):
<ul>
    <li><tt>readme.html</tt> -- Optional readme file for plugin, which will be displayed to end users.</li>
156

157
    <li><tt>changelog.html</tt> -- Optional changelog file for plugin, which will be displayed to end users.</li>
Matt Tucker's avatar
Matt Tucker committed
158 159
    <li><tt>logo_small.png</tt> -- Optional small (16x16) icon associated with the plugin. It can also be a .gif file.</li>
    <li><tt>logo_large.png</tt> -- Optional large (32x32) icon associated with the plugin. It can also be a .gif file.</li>
160 161
</ul>

162
<p>Your plugin class must be implement the
163
<tt><a href="javadoc/org/jivesoftware/openfire/container/Plugin.html">Plugin</a></tt>
164
interface from the <a href="javadoc/index.html">Openfire API</a> as
Matt Tucker's avatar
Matt Tucker committed
165
well as have a default (no argument) contructor. The Plugin interface has
166
methods for initializing and destroying the plugin.
167 168 169 170 171 172 173
</p>

<fieldset>
    <legend>Sample plugin implementation</legend>
<pre class="java">
package org.example;

174 175
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
176 177 178 179

import java.io.File;

/**
180
 * A sample plugin for Openfire.
181 182 183
 */
public class ExamplePlugin implements Plugin {

184
    public void initializePlugin(PluginManager manager, File pluginDirectory) {
185
        <span class="comment">// Your code goes here</span>
186

187 188
    }

189
    public void destroyPlugin() {
190 191 192 193 194
        <span class="comment">// Your code goes here</span>
    }
}
</pre>
</fieldset>
Matt Tucker's avatar
Matt Tucker committed
195

196 197 198 199 200 201 202 203 204
<h3>General Plugin Best Practices</h3>

<p>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.</p>.

Matt Tucker's avatar
Matt Tucker committed
205 206 207
<h2>Modifying the Admin Console</h2>

<p>Plugins can add tabs, sections, and pages to the admin console. There
Matt Tucker's avatar
Matt Tucker committed
208
are a several steps to accomplishing this:
Matt Tucker's avatar
Matt Tucker committed
209 210

<ul>
211 212 213
    <li>An &lt;adminconsole/&gt; section must be added to the
            <tt>plugin.xml</tt> file.
    </li>
214

215 216 217
    <li>JSP files must be compiled and put into the classpath of the
        plugin. A <tt>web.xml</tt> file containing the compiled JSP
        servlet entries must be put into the <tt>web/</tt> directory
218
        of the plugin. <i>Note:</i> the Openfire build script
219 220 221 222 223 224
        can assist with compiling JSPs and creating the web.xml. This
        is detailed below.
    </li>
    <li>Any images required by your JSP pages must live in <tt>web/images/</tt>
        directory. Only GIF and PNG images are supported.
    </li>
225

Matt Tucker's avatar
Matt Tucker committed
226
</ul>
Matt Tucker's avatar
Matt Tucker committed
227

228
<p>The <tt>&lt;adminconsole /&gt;</tt> section of <tt>plugin.xml</tt> defines additional
Matt Tucker's avatar
Matt Tucker committed
229 230
tabs, sections and entries in the Admin Console framework. A sample
<tt>plugin.xml</tt> file might look like the following:</p>
Matt Tucker's avatar
Matt Tucker committed
231

232 233
<fieldset>
    <legend>Sample plugin.xml</legend>
234

235
<pre class="xml">
Matt Tucker's avatar
Matt Tucker committed
236 237
&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;plugin&gt;
238
    <span class="comment">&lt;!-- Main plugin class --&gt;</span>
Matt Tucker's avatar
Matt Tucker committed
239
    &lt;class&gt;org.example.ExamplePlugin&lt;/class&gt;
240 241

    <span class="comment">&lt;!-- Admin console entries --&gt;</span>
242

243 244 245 246 247 248 249 250
    &lt;adminconsole&gt;
        &lt;tab id="mytab" name="Example" url="my-plugin-admin.jsp" description="Click to manage..."&gt;
            &lt;sidebar id="mysidebar" name="My Plugin"&gt;
               &lt;item id="my-plugin" name="My Plugin Admin"
                   url="my-plugin-admin.jsp"
                   description="Click to administer settings for my plugin" /&gt;
            &lt;/sidebar&gt;
        &lt;/tab&gt;
251

252
    &lt;/adminconsole&gt;
Matt Tucker's avatar
Matt Tucker committed
253
&lt;/plugin&gt;
254 255
</pre>
</fieldset>
Matt Tucker's avatar
Matt Tucker committed
256

257 258 259
<p>
In this example, we've defined a new tab "Example", a sidebar section
"My Plugin" and a page "My Plugin Admin". We've registered <tt>my-plugin-admin.jsp</tt>
Matt Tucker's avatar
Matt Tucker committed
260 261
as the page. You can override existing tabs, sections, and items by using
the existing id attribute values in your own <tt>&lt;adminconsole&gt;</tt> defintion.
262 263
</p>

Matt Tucker's avatar
Matt Tucker committed
264 265 266
<h3>Admin Console Best Practices</h3>

There are several best practices to consider when making changes to
267
the Openfire admin console via a plugin. The general theme is
Matt Tucker's avatar
Matt Tucker committed
268 269 270 271
that plugins should integrate seamlessly:

<ul>
		<li>Integrate into existing tabs and sidebar sections whenever possible
272 273
            instead of creating your own. Only create new tabs for very
            significant new functionality.
Matt Tucker's avatar
Matt Tucker committed
274
		<li>Don't use the word "plugin" in names of tabs, sidebars and items.
275 276
            For example, instead of having an item called "Gateway Plugin", it
            could be called "Gateway Settings".
Matt Tucker's avatar
Matt Tucker committed
277 278 279
		<li>Try to match the UI of the existing admin console in your custom
		    plugin pages.
		<li>There is no need to create an admin console entry to show plugin
280
	        meta-data. Instead, let Openfire inform the user about which
281
            plugins are installed and provide plugin management.
Matt Tucker's avatar
Matt Tucker committed
282
</ul>
Matt Tucker's avatar
Matt Tucker committed
283

284 285 286
<h3>Writing Pages for the Admin Console</h3>

<p>
287

288
Openfire uses the <a href="http://www.opensymphony.com/sitemesh/" target="_blank">Sitemesh</a>
289 290 291 292 293 294 295 296 297 298 299 300 301
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:</p>
<br>
<div align="center"><img src="images/sitemesh.png" width="484" height="372" alt="Sitemesh"></div>
<br><br>
<p>
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:
<ul>
    <li><b>pageID</b> -- the ID of the page, which must match an entry in the admin console
    XML described above. Either a pageID or subPageID <b>must</b> be specified.</li>
302

303 304 305 306 307 308 309 310 311 312
    <li><b>subPageID</b> -- the ID of the sub-page, which must match an entry in the
    admin console XML described above. Sub-pages are used for administrative actions
    related to a parent page ID. For example, editing or deleting a particular group.
    Either a pageID or subPageID <b>must</b> be specified.</li>
    <li><b>extraParams</b> (Optional) -- extra parameters that should be passed in to the page.
    For example, on a page to delete a group it might be the ID of the group. Parameters
    must be URL encoded.</li>
    <li><b>decorator</b> (Optional) -- overrides the Sitemesh decorator to use for the page.
    A decorator named <tt>none</tt> is available that will simply render the page
    without a decorator.</li>
313

314 315 316 317 318 319 320 321 322 323 324
</ul>

The following HTML snippet demonstrates a valid page:
</p>

<fieldset>
    <legend>Sample HTML</legend>
<pre>
   &lt;html&gt;
   &lt;head&gt;
       &lt;title&gt;My Plugin Page&lt;/title&gt;
325

326 327 328 329 330 331 332
       &lt;meta name="pageID" content="myPluginPage"/&gt;
   &lt;/head&gt;
   &lt;body&gt;
        Body here!
   &lt;/body&gt;
   &lt;/html&gt;
</pre>
333

334 335
</fieldset>

336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
<h4>Using i18n in your Plugins</h4>
<p>
It's possible to translate your plugin into multiple languages (i18n). To do so, use the following
procedure:
<ul>
    <li>Create a "i18n" directory in the root directory of your plugin.</li>
    <li>Add each resource file using the %[plugin_name]%_i18n "_" language ".properties"
        naming convention, where [plugin_name] is the name of the plugin directory. See the
        <a href="translator-guide.html">translator guide</a> for more information about resource
        bundles.</li>
    <li>Convert Strings in your JSP files to refer to the internationalized keys. For example:

        <pre>
        &lt;%@ taglib uri="http://java.sun.com/jstl/core_rt" prefix="c" %&gt;
        &lt;%@ taglib uri="http://java.sun.com/jstl/fmt_rt" prefix="fmt" %&gt;
        ...
        &lt;fmt:message key="some.key.name" /&gt;
        </pre>
    </li>
355 356 357 358 359 360 361
    <li>Internationalize Strings in your Java files using the LocaleUtils class:<br>
        <tt>org.jivesoftware.util.LocaleUtils.getLocalizedString("some.key.name", "[plugin_name]");</tt>
    </li>
    <li>Internationalize Strings in your plugin.xml file using the ${var} format:<br>
        <tt>&lt;sidebar id="gateways" name="${plugin.sidebar.name}" description="${plugin.sidebar.description}"&gt;</tt><br>
        <tt>&lt;description&gt;${plugin.description}&lt;/description&gt;</tt>
    </li>
362 363
</ul>
</p>
364
<h2>Using the Openfire Build Script</h2>
Matt Tucker's avatar
Matt Tucker committed
365

366
<p>
367
The Openfire build script will help you build and develop plugins. It
Matt Tucker's avatar
Matt Tucker committed
368
looks for plugin development directories in the following format:
369 370
</p>

Matt Tucker's avatar
Matt Tucker committed
371 372 373
<fieldset>
    <legend>Plugin Structure</legend>
<pre>myplugin/
374 375 376
 |- plugin.xml      &lt;- Plugin definition file
 |- readme.html     &lt;- Optional readme file for plugin
 |- changelog.html  &lt;- Optional changelog file for plugin
Matt Tucker's avatar
Matt Tucker committed
377 378
 |- logo_small.gif  &lt;- Optional small (16x16) icon associated with the plugin (can also be a .png file)
 |- logo_large.gif  &lt;- Optional large (32x32) icon associated with the plugin (can also be a .png file)
379 380
 |- classes/        &lt;- Resources your plugin needs (i.e., a properties file)
 |- lib/            &lt;- Libraries your plugin needs
Matt Tucker's avatar
Matt Tucker committed
381
 |- src/
382
     |- database    &lt;- Optional database scripts for your plugin
383
     |- java        &lt;- Java source code for your plugin
Matt Tucker's avatar
Matt Tucker committed
384 385 386 387 388 389
     |   |- com
     |       |- mycompany
     |           |- *.java
     |- web
         |- *.jsp      &lt;- JSPs your plugin uses for the admin console
         |- images/    &lt;- Any images your JSP pages need (optional)
390
         |- WEB-INF
Matt Tucker's avatar
Matt Tucker committed
391
             |- web.xml    &lt;- Optional file where custom servlets can be registered
392

Matt Tucker's avatar
Matt Tucker committed
393 394 395
</pre>
</fieldset>

Matt Tucker's avatar
Matt Tucker committed
396
<p>The build script will compile source files and JSPs and create a valid
Matt Tucker's avatar
Matt Tucker committed
397 398 399 400
plugin structure and JAR file. Put your plugin directories in the <tt>src/plugins</tt>
directory of the source distribution and then use <tt>ant plugins</tt> to
build your plugins.</p>

401 402
<p>Any JAR files your plugin needs during compilation should be put
into the <tt>lib</tt> directory. These JAR files will also be copied into
403 404 405 406 407 408 409
the plugin's generated <tt>lib</tt> directory as part of the build process.</p>

<p>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.</p>
410

Matt Tucker's avatar
Matt Tucker committed
411 412
<h2>Implementing Your Plugin</h2>

413
<p>Plugins have full access to the Openfire API. This provides a tremendous
Matt Tucker's avatar
Matt Tucker committed
414 415 416 417
amount of flexibility for what plugins can accomplish. However, there are several integration
points that are the most common:

<ol>
Matt Tucker's avatar
Matt Tucker committed
418
    <li>Register a plugin as a <a href="javadoc/org/xmpp/component/Component.html">Component</a>.
Matt Tucker's avatar
Matt Tucker committed
419 420 421 422
 Components receive all packets addressed to a particular sub-domain. For example,
 <tt>test_component.example.com</tt>. So, a packet sent to <tt>joe@test_component.example.com</tt> 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
Matt Tucker's avatar
Matt Tucker committed
423
 example above); sub-domains are only used for routing within the XMPP server.
Matt Tucker's avatar
Matt Tucker committed
424

425
    <li>Register a plugin as an <a href="javadoc/org/jivesoftware/openfire/IQHandler.html">IQHandler</a>. IQ handlers respond to IQ packets with a particular element name and
426
  namespace. The following code snippet demonstrates how to register an IQHandler:
Matt Tucker's avatar
Matt Tucker committed
427

428
  <pre>
429

430 431 432 433
  IQHandler myHandler = new MyIQHander();
  IQRouter iqRouter = XMPPServer.getInstance().getIQRouter();
  iqRouter.addHandler(myHandler);
  </pre>
Matt Tucker's avatar
Matt Tucker committed
434

435
    <li>Register a plugin as a <a href="javadoc/org/jivesoftware/openfire/interceptor/PacketInterceptor.html">
436 437 438
    PacketInterceptor</a> 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.</li>
439
    <li>You can store persistent plugin settings as Openfire properties using the
440 441
    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
442
    <tt>org.jivesoftware.util.PropertyEventListener</tt> method.
443 444 445
    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.

446

Matt Tucker's avatar
Matt Tucker committed
447 448 449
</ol>

</p>
Matt Tucker's avatar
Matt Tucker committed
450

Matt Tucker's avatar
Matt Tucker committed
451 452 453 454
<h2>Plugin FAQ</h2>

<b>Can I deploy a plugin as a directory instead of a JAR?</b>
<p>No, all plugins must be deployed as JAR or WAR files. When a JAR or WAR is not present for the plugin,
455
Openfire assumes that the file has been deleted and that the users wants to destroy the plugin,
Matt Tucker's avatar
Matt Tucker committed
456 457
so it also deletes the directory.</p>

458 459 460 461 462 463
<br>
<br>

	</div>

</div>
464

Matt Tucker's avatar
Matt Tucker committed
465 466
</body>
</html>