Writing Plugins

Everything in AxKit2 is controlled via plugins. This helps keep the core of AxKit2 very simple and lightweight, yet makes it very powerful and extensible.

Although AxKit comes with some included plugins to deliver flat files, and some demo plugins to show how to transform XML, the real power of AxKit2 will only be realised when you write your own plugin. Don't be afraid - this is very simple to do.

To make life easy, lets step through a working example.

A Simple XSLT Engine

We'll start with a simple plugin that transforms XML files to HTML using XSLT. This example will cover some of the basics of how the XML transformation system of AxKit2 works.

Setting up the Directories

Start in the AxKit2 root directory (where you can run ./axkit from). First we'll create a directory to store your XML and your stylesheets:

$ mkdir myfirstplugin
$ mkdir myfirstplugin/webroot
$ mkdir myfirstplugin/stylesheets
$ mkdir myfirstplugin/plugins

Server Configuration

Now we need to tell AxKit where all our files are and what port to listen on. Here's a minimal config which you should store in myfirstplugin/axkit.conf:

# Setup error logging to the maximum level:
Plugin    logging/warn
LogLevel  LOGDEBUG

# we need to load this to map URLs to filenames
Plugin uri_to_file

# Setup our server
<Server>
  Port 8080
  DocumentRoot myfirstplugin/webroot
  DirectoryIndex index.xml
  
  # Load our plugin
  PluginDir myfirstplugin/plugins
  Plugin    serve_xml
</Server>

Now we can try and start AxKit and see what happens:

$ ./axkit -c myfirstplugin/axkit.conf
L7 uri_to_file register_hook: uri_translation => hook_uri_translation
could not open myfirstplugin/plugins/serve_xml: No such file or directory
at lib/AxKit2/Plugin.pm line 103, <$fh> line 13.

Well we should have expected this -- we haven't written our plugin yet!

Plugin Code

So load up your editor on myfirstplugin/plugins/serve_xml and enter the following perl code:

#!/usr/bin/perl -w

sub hook_xmlresponse {
  my ($self, $input) = @_;
  
  $self->log(LOGDEBUG, "Serving up a file");
  
  my $stylesheet = './myfirstplugin/stylesheets/default.xsl';
  
  my $out = $input->transform(XSLT($stylesheet));
  
  return OK, $out;
}

The key part of this is the $input->transform(...) method call, which transforms the input using the given transformation types. Available types include XSP(), TAL(), XPathScript() and of course XSLT().

Once the transformation is done we return OK, $out to let AxKit know that the transformation was successful and we should deliver the result to the browser.

[As a sidenote here, I include the #! perl shebang line to help syntax highlighters know that this is perl code.]

Now if you run AxKit again we shouldn't see any error, but instead some debug output telling us our plugin was loaded:

$ ./axkit -c myfirstplugin/axkit.conf
L7 uri_to_file register_hook: uri_translation => hook_uri_translation
L7 serve_xml register_hook: xmlresponse => hook_xmlresponse

Now if we point a web browser at this server we will get an error because we haven't created an index.xml and default.xsl yet. So lets do that now.

XML and XSLT Files

myfirstplugin/webroot/index.xml:

<?xml version="1.0"?>
<dromedaries>
  <species name="Camel">
    <humps>1 or 2</humps>
    <disposition>Cranky</disposition>
  </species>
  <species name="Llama">
    <humps>1 (sort of)</humps>
    <disposition>Aloof</disposition>
  </species>
  <species name="Alpaca">
    <humps>(see Llama)</humps>
    <disposition>Friendly</disposition>
  </species>
</dromedaries>

myfirstplugin/stylesheets/default.xsl:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
  <html>
  <head><title>Know Your Dromedaries</title></head>
  <body>
    <table bgcolor="eeeeee" border="1">
    <tr>
    <th>Species</th>
    <th>No of Humps</th>
    <th>Disposition</th>
    </tr>
    <xsl:for-each select="dromedaries">
      <xsl:apply-templates select="./species" />
  </xsl:for-each>
  </table>
  </body>
  </html>
</xsl:template>
<xsl:template match="species">
  <tr>
  <td><xsl:value-of select="@name" /></td>
  <td><xsl:value-of select="./humps" /></td>
  <td><xsl:value-of select="./disposition" /></td>
  </tr>
</xsl:template>
</xsl:stylesheet>

Now when you request the page you should get a HTML table listing the dromedaries in the index.xml file.

Some browsers might also request the non-existant favicon.ico too, which may cause an error to appear in the terminal. Don't worry about that for now.

Congratulations, your first plugin is complete!

Making Your Plugin Configurable

One area where AxKit2 really shines is that each plugin can define its own configuration parameters. Our plugin is a perfect candidate for that because we hard-coded the XSLT stylesheet. Lets make that configurable now.

To add configuration parameters we need to specify them as soon as the plugin is loaded, in the plugin's init() method.

    A Sidetrack - What is a 'Plugin'?

    Skip this section if you don't want to know the gory details - you don't need to know about for writing simple plugins.

    Knowledgable perl hackers will have noticed that our hook_xmlresponse method above received two parameters, the first being $self, and yet we didn't create a perl class with all the annoying bless verbiage. Well yes, a plugin is a perl class, and $self is an instance of that class. We simply went out of our way to hide all the horrible perl guts from you.

    If you prefer all the perl guts to be exposed, you can create a full blown package as your plugin instead, install it in the usual perl install locations, and load it with:

    Plugin My::Plugin

    As long as it has '::' in the name, AxKit will recognise it as a perl module and load it that way. Just be sure to 'use base q(AxKit2::Plugin)' so you get all the base class methods.

The init() method allows us to setup any plugin-wide defaults such as database connections. To add configuration directives we just call the register_config() method like so:

sub init {
  my $self = shift;
  $self->register_config('XSLT_Style', sub { $self->set_style(@_) });
}

There we added a configuration directive called XSLT_Style which we'll use to define our stylesheet. We just need to implement the set_style() method:

sub set_style {
  my ($self, $config, $style) = @_;
  my $key = $self->plugin_name . '::style';
  $config->notes($key, $style);
}

There we store the configuration in the $config-notes()> table (mod_perl users will recognise this as a dictionary storage container), making sure we don't clobber anyone elses name by using the key plugin_name::style.

Now we also need the corresponding get_style() method:

sub get_style {
  my $self = shift;
  $self->config->notes($self->plugin_name . '::style');
}

Simple huh?

Now we need to tie all that together. First add this to your config after "Plugin serve_xml":

XSLT_Style myfirstplugin/stylesheets/default.xsl

And now modify your hook_xmlresponse() method to get this new value using get_style():

sub hook_xmlresponse {
  my ($self, $input) = @_;
  $self->log(LOGDEBUG, "Serving up a file");
  
  my $stylesheet = $self->get_style;
  my $out = $input->transform(XSLT($stylesheet));
  
  return OK, $out;
}

And we're done! Run (or restart) axkit (./axkit -c myfirstplugin/axkit.conf) and ensure that it all works.

Chained Transformations

One of the powerful features of AxKit was always being able to easily chain transformations together, this makes complex XSLT transformations much simpler, among other benefits.

Lets modify our plugin to support multiple XSLT transformations.

The simplest way to do this is to modify our XSLT_Style directive to support multiple values, each value being a new XSLT stylesheet. First lets modify our configuration to detail what we want:

XSLT_Style myfirstplugin/stylesheets/sort.xsl \
           myfirstplugin/stylesheets/default.xsl

The idea here being that one XSLT will sort our dromedaries, and the second will turn them into HTML.

Lets create our sort.xsl file first:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<xsl:stylesheet 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  version="1.0">

<xsl:template match="dromedaries">
<dromedaries>
  <xsl:apply-templates select="species">
      <xsl:sort select="@name"/>
  </xsl:apply-templates>
</dromedaries>
</xsl:template>

<!-- Identity Template -->
<xsl:template match="*|@*">
  <xsl:copy>
    <xsl:copy-of select="@*"/>
    <xsl:apply-templates/>
  </xsl:copy>
</xsl:template>
</xsl:stylesheet>

Note the use of the identity template. If you haven't used XSLT before take note of that - it's a very useful template for debugging.

So now we need to store a list of XSLT stylesheets in our config, and make use of a list in the ->transform() method.

The nice thing about register_config() is that it automatically copes with multi-valued configuration. All we need to do is cope with storing multiple values. So we change our set_style() sub as follows:

sub set_style {
  my ($self, $config, @styles) = @_;
  my $key = $self->plugin_name . '::style';
  $config->notes($key, \@styles);
}

So rather than taking a single value we take an array and store a reference to that array.

We don't need to change get_style() as that still returns the right "key" - we just need to modify our expectations when we call get_style():

sub hook_xmlresponse {
  my ($self, $input) = @_;
  $self->log(LOGDEBUG, "Serving up a file");
  
  my $stylesheets = $self->get_style;
  my $out = $input->transform(map XSLT($_), @$stylesheets);
  
  return OK, $out;
}

So instead of running XSLT() once, we map it over @$stylesheets.

And that's it. Restart AxKit and make sure this returns a table of sorted results.

What Are the APIs?