=head1 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.

=head1 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.

=head2 Setting up the Directories

Start in the AxKit2 root directory (where you can run C<./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

=head2 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 F<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!

=head2 Plugin Code

So load up your editor on F<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 C<< $input->transform(...) >> method call, which
transforms the input using the given transformation types. Available types
include C<XSP()>, C<TAL()>, C<XPathScript()> and of course C<XSLT()>.

Once the transformation is done we return C<< 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 F<index.xml> and F<default.xsl> yet. So lets do that now.

=head2 XML and XSLT Files

F<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>

F<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!



=head1 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 C<init()> method.



=over 4

B<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 C<hook_xmlresponse> method
above received two parameters, the first being C<$self>, and yet we didn't
create a perl class with all the annoying C<bless> verbiage. Well yes, a plugin
is a perl class, and C<$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.

=back



The C<init()> method allows us to setup any plugin-wide defaults such as
database connections. To add configuration directives we just call the
C<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 C<XSLT_Style> which we'll use
to define our stylesheet. We just need to implement the C<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 C<$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 I<plugin_name>::style.

Now we also need the corresponding C<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
"C<Plugin serve_xml>":

  XSLT_Style myfirstplugin/stylesheets/default.xsl

And now modify your C<hook_xmlresponse()> method to get this new value using
C<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 (C<./axkit -c myfirstplugin/axkit.conf>)
and ensure that it all works.




=head1 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 C<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 F<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 C<< ->transform() >> method.

The nice thing about C<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 C<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 C<get_style()> as that still returns the right "key" - we
just need to modify our expectations when we call C<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 C<XSLT()> once, we map it over C<@$stylesheets>.

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





=head1 What Are the APIs?



=cut
