source: trunk/doc/src/docbook/developerdoc/plugin_developer.xml @ 3170

Last change on this file since 3170 was 3170, checked in by Johan Enell, 16 years ago

jsp section added and some examples removed

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 42.4 KB
Line 
1<?xml version="1.0" encoding="UTF-8"?>
2<!DOCTYPE chapter PUBLIC
3    "-//Dawid Weiss//DTD DocBook V3.1-Based Extension for XML and graphics inclusion//EN"
4    "http://www.cs.put.poznan.pl/dweiss/dtd/dweiss-docbook-extensions.dtd">
5<!--
6  $Id: plugin_developer.xml 3170 2007-03-07 17:15:18Z enell $:
7 
8  Copyright (C) Authors contributing to this file.
9 
10  This file is part of BASE - BioArray Software Environment.
11  Available at http://base.thep.lu.se/
12 
13  BASE is free software; you can redistribute it and/or
14  modify it under the terms of the GNU General Public License
15  as published by the Free Software Foundation; either version 2
16  of the License, or (at your option) any later version.
17 
18  BASE is distributed in the hope that it will be useful,
19  but WITHOUT ANY WARRANTY; without even the implied warranty of
20  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  GNU General Public License for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with this program; if not, write to the Free Software
25  Foundation, Inc., 59 Temple Place - Suite 330,
26  Boston, MA  02111-1307, USA.
27-->
28
29<chapter id="plugin_developer">
30  <title>Plugin developer</title>
31  <sect1 id="plugin_developer.interfaces">
32    <title>The plugin interfaces</title>
33    <para>
34      The Base2 core defined two interfaces that are vital for implementing plugins.
35      <itemizedlist spacing="compact">
36        <listitem>
37          <simpara>net.sf.basedb.core.plugin.Plugin</simpara>
38        </listitem>
39        <listitem>
40          <simpara>net.sf.basedb.core.plugin.InteractivePlugin</simpara>
41        </listitem>
42      </itemizedlist>
43      It is required that the
44      <interfacename>Plugin</interfacename>
45      interface is implemented, but the
46      <interfacename>InteractivePlugin</interfacename>
47      is optional, and is only needed if you want user interaction.
48    </para>
49
50    <sect2 id="plugin_developer.interfaces.plugin">
51      <title>The Plugin interface</title>
52      <para>This interface defines seven methods and must be implemented by all plugins.</para>
53      <variablelist>
54        <varlistentry>
55          <term>
56            <methodsynopsis language="java">
57              <modifier>public</modifier>
58              <type>About</type>
59              <methodname>getAbout</methodname>
60              <void />
61            </methodsynopsis>
62          </term>
63          <listitem>
64            <para>
65              Return information about the plugin, i.e. the name, version, and a short
66              description about what the plugin does. The
67              <classname>About</classname>
68              object also has fields for naming the author and various other contact
69              information. The returned information is copied by the core at
70              installation time into the database. The only required information is
71              the name of the plugin. All other fields may have null values.
72            </para>
73            <example id="net.sf.basedb.core.plugin.Plugin.getAbout">
74              <title>A typical implementation stores this information in a static field</title>
75              <programlisting>
76private static final About about = new AboutImpl
77(
78   "Spot images creator",
79   "Converts a full-size scanned image into smaller preview jpg " +
80   "images for each individual spot.",
81   "2.0",
82   "2006, Department of Theoretical Physics, Lund University",
83   null,
84   "base@thep.lu.se",
85   "http://base.thep.lu.se"
86);
87 
88public About getAbout()
89{
90   return about;
91}
92              </programlisting>
93            </example>
94          </listitem>
95        </varlistentry>
96        <varlistentry>
97          <term>
98            <methodsynopsis language="java">
99              <modifier>public</modifier>
100              <type>Plugin.MainType</type>
101              <methodname>getMainType</methodname>
102              <void />
103            </methodsynopsis>
104          </term>
105          <listitem>
106            <para>
107              Return information about the main type of plugin. The
108              <classname>MainType</classname>
109              is an enumeration which defines five possible values:
110              <itemizedlist>
111                <listitem>
112                  <para>
113                    <constant>ANALYZE</constant>
114                    : An analysis plugin
115                  </para>
116                </listitem>
117                <listitem>
118                  <para>
119                    <constant>EXPORT</constant>
120                    : A plugin the exports data
121                  </para>
122                </listitem>
123                <listitem>
124                  <para>
125                    <constant>IMPORT</constant>
126                    : A plugin that imports data
127                  </para>
128                </listitem>
129                <listitem>
130                  <para>
131                    <constant>INTENSITY</constant>
132                    : A plugin that calculates the original spot intensities
133                    from raw data
134                  </para>
135                </listitem>
136                <listitem>
137                  <para>
138                    <constant>OTHER</constant>
139                    : Any other type of plugin
140                  </para>
141                </listitem>
142              </itemizedlist>
143              The returned value is stored in the database but is otherwise not used
144              by the core. Client applications (such as the web client) will probably
145              use this information to group the plugins, i.e., a button labeled Export
146              will let you select among the export plugins.
147            </para>
148            <example id="net.sf.basedb.core.plugin.Plugin.getMainType">
149              <title>A typical implementation just return one of the values</title>
150              <programlisting>
151public Plugin.MainType getMainType()
152{
153   return Plugin.MainType.OTHER;
154}
155              </programlisting>
156            </example>
157          </listitem>
158        </varlistentry>
159        <varlistentry>
160          <term>
161            <methodsynopsis language="java">
162              <modifier>public</modifier>
163              <type>boolean</type>
164              <methodname>supportsConfigurations</methodname>
165              <void />
166            </methodsynopsis>
167          </term>
168          <listitem>
169            <para>
170              If this method returns true the plugin can have different
171              configurations, (i.e.
172              <classname>PluginConfiguration</classname>
173              ). Note that this method may return true even if the
174              <interfacename>InteractivePlugin</interfacename>
175              interface isn't implemented. The
176              <classname>AbstractPlugin</classname>
177              returns true for this method which is the old way before the
178              introduction of this method.
179            </para>
180          </listitem>
181        </varlistentry>
182        <varlistentry>
183          <term>
184            <methodsynopsis language="java">
185              <modifier>public</modifier>
186              <type>boolean</type>
187              <methodname>requiresConfiguration</methodname>
188              <void />
189            </methodsynopsis>
190          </term>
191          <listitem>
192            <para>
193              If this method returns true a Job can't be created without a
194              configuration. The
195              <classname>AbstractPlugin</classname>
196              returns false for this method which is the old way before the
197              introduction of this method.
198            </para>
199          </listitem>
200        </varlistentry>
201        <varlistentry>
202          <term>
203            <methodsynopsis language="java">
204              <modifier>public</modifier>
205              <void />
206              <methodname>init</methodname>
207              <methodparam>
208                <type>SessionControl</type>
209                <parameter>sc</parameter>
210              </methodparam>
211              <methodparam>
212                <type>ParameterValues</type>
213                <parameter>configuration</parameter>
214              </methodparam>
215              <methodparam>
216                <type>ParameterValues</type>
217                <parameter>job</parameter>
218              </methodparam>
219            </methodsynopsis>
220          </term>
221          <listitem>
222            <para>
223              Prepare the plugin for execution (or configuration). If the plugin needs
224              to do some initialization this is the place to do it. A typical
225              implementation however only stores the passed parameters in instance
226              variables for later use.
227            </para>
228            <para>
229              The parameters passed to this method has vital information that is
230              needed to execute the plugin. The
231              <classname>SessionControl</classname>
232              is a central core object which holds information about the logged in
233              user and allows you to create
234              <classname>DbControl</classname>
235              objects which allows a plugin to connect to the database to read, add or
236              update information. The two
237              <classname>ParameterValues</classname>
238              objects contains information about the parameters to the plugin. The
239              configuration object holds all parameters stored together with a
240              <classname>PluginConfiguration</classname>
241              object in the database. The job object holds all parameters that are
242              stored together with a Job object in the database.
243            </para>
244            <para>
245              The difference between a plugin configuration and a job parameter is
246              that a configuration is usually something an administrator sets up,
247              while a job is an actual execution of a plugin. For example a
248              configuration for an import plugin holds the regular expressions needed
249              to parse a text file and find the headers, sections and data lines,
250              while the job holds the file to parse.
251            </para>
252            <para>
253              The
254              <classname>AbstractPlugin</classname>
255              contains an implementation of this method make the passed parameters
256              available as protected instance variables. We recommend plugin
257              developers to let their plugins extend this class since it also has some
258              other useful methods. For example for validating parameters resulting
259              from user interaction and to store these values in the database.
260            </para>
261            <example id="net.sf.basedb.core.plugin.Plugin.init">
262              <title>The <classname>AbstractPlugin</classname> implementation of this method</title>
263              <programlisting>
264protected SessionControl sc = null;
265protected ParameterValues configuration = null;
266protected ParameterValues job = null;
267/**
268   Store copies of the session control, plugin and job configuration. These
269   are available to subclasses in the {@link #sc}, {@link #configuration}
270   and {@link #job} variables. If a subclass overrides this method it is
271   recommended that it also calls super.init(sc, configuration, job).
272*/
273public void init(SessionControl sc,
274   ParameterValues configuration, ParameterValues job)
275   throws BaseException
276{
277   this.sc = sc;
278   this.configuration = configuration;
279   this.job = job;
280}
281              </programlisting>
282            </example>
283          </listitem>
284        </varlistentry>
285        <varlistentry>
286          <term>
287            <methodsynopsis language="java">
288              <modifier>public</modifier>
289              <void />
290              <methodname>run</methodname>
291              <methodparam>
292                <type>Request</type>
293                <parameter>request</parameter>
294              </methodparam>
295              <methodparam>
296                <type>Response</type>
297                <parameter>response</parameter>
298              </methodparam>
299              <methodparam>
300                <type>ProgressReporter</type>
301                <parameter>progress</parameter>
302              </methodparam>
303              <exceptionname>BaseException</exceptionname>
304            </methodsynopsis>
305          </term>
306          <listitem>
307            <para>
308              Runs the plugin. The
309              <classname>Request</classname>
310              parameter has no useful information and can be ignored. It was
311              originally used for passing parameters to the plugin but this is now
312              found in the two
313              <classname>ParameterValues</classname>
314              objects passed to the init method.
315            </para>
316            <para>
317              The
318              <classname>ProgressReporter</classname>
319              can be used by a plugin to report it's progress back to the core. The
320              core will usually send the progress information to the database, which
321              allows users to see exactly how the plugin is progressing from the web
322              interface. This parameter can be null, but if it isn't we recommend all
323              plugins to use it. However, it should be used sparingly, since each call
324              to set the progress results in a database update. If the execution
325              involves several thousands of items it is a bad idea to update the
326              progress after processing each one of them. A good starting point is to
327              divide the work into 100 pieces each representing 1% of the work, i.e.,
328              if the plugin should export 100 000 items it should report progress
329              after every 1000 items.
330            </para>
331            <para>
332              The
333              <classname>Response</classname>
334              parameter is used to tell the core if the plugin was successful or
335              failed. Not setting a response is considered a failure by the core. From
336              the run method it is only allowed to use the
337              <methodname>setDone</methodname>
338              () or the
339              <methodname>setError</methodname>
340              () methods.
341            </para>
342            <example id="net.sf.basedb.core.plugin.Plugin.run">
343              <title>
344                Here is a skeleton that we recommend each plugin to use in it's
345                implementation of the
346                <methodname>run</methodname>
347                method
348              </title>
349              <programlisting>
350public void run(Request request, Response response, ProgressReporter progress)
351{
352   // Open a connection to the database
353   // sc is set by init() method
354   DbControl dc = sc.newDbControl();
355   try
356   {
357      // Insert code for plugin here
358
359      // Commit the work
360      dc.commit();
361      response.setDone("Plugin ended successfully");
362   }
363   catch (Throwable t)
364   {
365      // All exceptions must be catched and sent back
366      // using the response object
367      response.setError(t.getMessage(), Arrays.asList(t));
368   }
369   finally
370   {
371      // IMPORTANT!!! Make sure opened connections are closed
372      if (dc != null) dc.close();
373   }
374}
375              </programlisting>
376            </example>
377          </listitem>
378        </varlistentry>
379        <varlistentry>
380          <term>
381            <methodsynopsis language="java">
382              <modifier>public</modifier>
383              <void />
384              <methodname>done</methodname>
385              <void />
386            </methodsynopsis>
387          </term>
388          <listitem>
389            <para>
390              Clean up all resources after executing the plugin. This method mustn't
391              throw any exceptions.
392            </para>
393            <example id="net.sf.basedb.core.plugin.Plugin.done">
394              <title>
395                The
396                <classname>AbstractPlugin</classname>
397                contains an implementation of this method which simply sets the
398                parameters passed to the
399                <methodname>init</methodname>
400                method to null
401              </title>
402              <programlisting>
403/**
404   Clears the variables set by the init method. If a subclass
405   overrides this method it is recommended that it also calls super.done().
406*/
407public void done()
408{
409   configuration = null;
410   job = null;
411   sc = null;
412}
413              </programlisting>
414            </example>
415          </listitem>
416        </varlistentry>
417      </variablelist>
418    </sect2>
419
420    <sect2 id="plugin_developer.interfaces.interactive">
421      <title>The InteractivePlugin interface</title>
422      <para>
423        If you want the plugin to be able to interact with the user you must also implement
424        this interface. This is probably the case for most plugins. Among the plugins
425        supplied with the core of Base the
426        <classname>SpotImageCreator</classname>
427        is one plugin that doesn't interact with the user. Instead, the web client has
428        special JSP pages that handles all the interaction, creates a job for it and sets
429        the parameters. This, kind of hardcoded, approach can be used for other plugins as
430        well, but then it usually requires modification of the client application as well.
431      </para>
432      <para>
433        The
434        <interfacename>InteractivePlugin</interfacename>
435        has three main tasks: tell a client application where the plugin should be plugged
436        in, ask users for parameters, and validate and store those parameters. It requires
437        the implementation of four method.
438      </para>
439      <variablelist>
440        <varlistentry>
441          <term>
442            <methodsynopsis language="java">
443              <modifier>public</modifier>
444              <type>Set&lt;GuiContext&gt;</type>
445              <methodname>getGuiContexts</methodname>
446              <void />
447            </methodsynopsis>
448          </term>
449          <listitem>
450            <para>
451              Return information about where the plugin should be plugged in. Each
452              place is identified by a
453              <classname>GuiContext</classname>
454              object, which is an
455              <classname>Item</classname>
456              and a
457              <classname>Type</classname>
458              . The item is one of the objects defined by the
459              <classname>net.sf.basedb.core.Item</classname>
460              enumeration and the type is either
461              <classname>Type.LIST</classname>
462              or
463              <classname>Type.ITEM</classname>
464              .
465            </para>
466            <para>
467              For example, the
468              <varname>GuiContext</varname>
469              = (
470              <constant>Item.REPORTER</constant>
471              ,
472              <constant>Type.LIST</constant>
473              ) tells a client application that this plugin can be plugged in whenever
474              a list of reporters is displayed. The
475              <varname>GuiContext</varname>
476              = (
477              <constant>Item.REPORTER</constant>
478              ,
479              <constant>Type.ITEM</constant>
480              ) tells a client application that this plugin can be plugged in whenever
481              a single reporter is displayed. The first case may be appropriate for a
482              plugin that imports or exports reporters. The second case may be used by
483              a plugin that updates the reporter information from an external source
484              (well, it may make sense to use this in the list case as well).
485            </para>
486            <para>
487              The returned information is copied by the core at installation time to
488              make it easy to ask for all plugins for a certain
489              <classname>GuiContext</classname>
490              .
491            </para>
492            <para>
493              A typical implementation creates a static unmodifiable
494              <classname>Set</classname>
495              which is returned by this method. It is important that the returned set
496              can't be modified, since it may be a security issue if a bad behaving
497              client application does that.
498            </para>
499            <example id="net.sf.basedb.core.plugin.InteractivePlugin.getGuiContexts">
500              <title>
501                A typical implementation of
502                <methodname>getGuiContexts</methodname>
503              </title>
504              <programlisting>
505// From the net.sf.basedb.plugins.RawDataFlatFileImporter plugin
506private static final Set&lt;GuiContext&gt; guiContexts =
507   Collections.singleton(new GuiContext(Item.RAWBIOASSAY, GuiContext.Type.ITEM));
508
509public Set&lt;GuiContext&gt; <methodname>getGuiContexts</methodname>()
510{
511   return <returnvalue>guiContexts</returnvalue>;
512}
513              </programlisting>
514            </example>
515          </listitem>
516        </varlistentry>
517        <varlistentry>
518          <term>
519            <methodsynopsis language="java">
520              <modifier>public</modifier>
521              <type>String</type>
522              <methodname>isInContext</methodname>
523              <methodparam>
524                <type>GuiContext</type>
525                <parameter>context</parameter>
526              </methodparam>
527              <methodparam>
528                <type>Object</type>
529                <parameter>item</parameter>
530              </methodparam>
531            </methodsynopsis>
532          </term>
533          <listitem>
534            <para>
535              This method is called to check if a particular item is usable for the
536              plugin, when the context type is
537              <constant>Type.ITEM</constant>
538              , i.e. the user has selected a specific sample and the the client
539              application is now displaying information about that sample. Thus, our
540              <varname>GuiContext</varname>
541              = (
542              <constant>Item.SAMPLE</constant>
543              ,
544              <constant>Type.ITEM</constant>
545              ). Now, the client application asks for a list of plugins supporting
546              this context and for each one in the list calls this method with the
547              current sample as the item parameter. The plugin should answer if it can
548              do whatever it is supposed to do by returning null or a string
549              containing a message why it can't.
550            </para>
551            <para>
552              Here is a real example from the
553              <classname>RawDataFlatFileImporter</classname>
554              plugin which imports raw data to a
555              <classname>RawBioAssay</classname>
556              . Thus,
557              <varname>GuiContext</varname>
558              = (
559              <constant>Item.RAWBIOASSAY</constant>
560              ,
561              <constant>Type.ITEM</constant>
562              ), but the plugin can only import data if there isn't any already, and
563              if the raw bioassay has the same raw data type as the plugin has been
564              configured for.
565            </para>
566            <example id="net.sf.basedb.core.plugin.InteractivePlugin.isInContext">
567              <title>
568                A simple implementation of
569                <methodname>isInContext</methodname>
570              </title>
571              <programlisting>
572/**
573   Returns null if the item is a {@link RawBioAssay} of the correct
574   {@link RawDataType} and doesn't already have spots.
575*/
576public String isInContext(GuiContext context, Object item)
577{
578   String message = null;
579   if (item == null)
580   {
581      message = "The object is null";
582   }
583   else if (!(item instanceof RawBioAssay))
584   {
585      message = "The object is not a RawBioAssay: " + item;
586   }
587   else
588   {
589      RawBioAssay rba = (RawBioAssay)item;
590      String rawDataType = (String)configuration.getValue("rawDataType");
591      if (rba.getSpots() > 0)
592      {
593         message = "The raw bioassay already has spots: " + rba.getName();
594      }
595      else if (!rba.getRawDataType().getId().equals(rawDataType))
596      {
597         message = "Unsupported raw data type: " + rba.getRawDataType().getName();
598      }
599   }
600   return message;   
601}
602              </programlisting>
603            </example>
604          </listitem>
605        </varlistentry>
606        <varlistentry>
607          <term>
608            <methodsynopsis language="java">
609              <modifier>public</modifier>
610              <type>RequestInformation</type>
611              <methodname>getRequestInformation</methodname>
612              <methodparam>
613                <type>GuiContext</type>
614                <parameter>context</parameter>
615              </methodparam>
616              <methodparam>
617                <type>String</type>
618                <parameter>command</parameter>
619              </methodparam>
620              <exceptionname>BaseException</exceptionname>
621            </methodsynopsis>
622          </term>
623          <listitem>
624            <para>
625              Ask the plugin for parameters that needs to be entered by the user. The
626              <classname>GuiContext</classname>
627              parameter is one of the contexts returned by the
628              <methodname>getGuiContexts</methodname>
629              method. The command is string telling the plugin what command was
630              executed. There are two predefined commands but as you will see the
631              plugin may define it's own commands. The two predefined commands are
632              defined in the
633              <classname>net.sf.basedb.core.plugin.Request</classname>
634              class.
635              <variablelist>
636                <varlistentry>
637                  <term>
638                    <constant>Request.COMMAND_CONFIGURE_PLUGIN</constant>
639                  </term>
640                  <listitem>
641                    <para>
642                      Used when an administrator is initiating a configuration
643                      of the plugin.
644                    </para>
645                  </listitem>
646                </varlistentry>
647                <varlistentry>
648                  <term>
649                    <constant>Request.COMMAND_CONFIGURE_JOB</constant>
650                  </term>
651                  <listitem>
652                    <para>
653                      Used when a user has selected the plugin for running a
654                      job.
655                    </para>
656                  </listitem>
657                </varlistentry>
658              </variablelist>
659              Given this information the plugin must return a
660              <classname>RequestInformation</classname>
661              object. This is simply a title, a description and a list of parameters.
662              Usually the title will end up as the input form title and the
663              description as a help text for the entire form. Do not put information
664              about the individual parameters in this description, since each
665              parameter has a description of their own.
666            </para>
667            <example id="net.sf.basedb.core.plugin.InteractivePlugin.getRequestInformation_1">
668              <title>
669                When running an import plugin it needs to ask for the file to import
670                from and if existing items should be updated or not
671              </title>
672              <programlisting>
673// The complete request information
674private RequestInformation configure Job;
675
676// The parameter that asks for a file to import from
677private PluginParameter&lt;File&gt; file Parameter;
678
679// The parameter that asks if existing items should be updated or not
680private PluginParameter&lt;Boolean&gt; updateExistingParameter;
681
682public RequestInformation getRequestInformation(GuiContext context, String command)
683   throws BaseException
684{
685   RequestInformation requestInformation = null;
686   if (command.equals(Request.COMMAND_CONFIGURE_PLUGIN))
687   {
688      requestInformation = getConfigurePlugin();
689   }
690   else if (command.equals(Request.COMMAND_CONFIGURE_JOB))
691   {
692      requestInformation = getConfigureJob();
693   }
694   return requestInformation;
695}
696
697/**
698   Get (and build) the request information for starting a job.
699*/
700private RequestInformation getConfigureJob()
701{
702   if (configureJob == null)
703   {
704      fileParameter = new PluginParameter&lt;File&gt;(
705         "file",
706         "File",
707         "The file to import the data from",
708         new FileParameterType(null, true, 1)
709      );
710     
711      updateExistingParameter = new PluginParameter&lt;Boolean&gt;(
712         "updateExisting",
713         "Update existing items",
714         "If this option is selected, already existing items will be updated " +
715         " with the information in the file. If this option isn't selected " +
716         " existing items are left untouched.",
717         new BooleanParameterType(false, true)
718      );
719
720      List&lt;PluginParameter&lt;?&gt;&gt; parameters =
721         new ArrayList&lt;PluginParameter&lt;?&gt;&gt;(2);
722      parameters.add(fileParameter);
723      parameters.add(updateExistingParameter);
724     
725      configureJob = new RequestInformation
726      (
727         Request.COMMAND_CONFIGURE_JOB,
728         "Select a file to import items from",
729         "Description",
730         parameters
731      );
732   }
733   return configureJob;
734}
735              </programlisting>
736            </example>
737            <para>
738              As you can see it takes some code to put together a
739              <classname>RequestInformation</classname>
740              object. For each parameter needed you need one
741              <classname>PluginParameter</classname>
742              object and one
743              <classname>ParameterType</classname>
744              object. Actually, a
745              <classname>ParameterType</classname>
746              can be reused for more than one
747              <classname>PluginParameter</classname>
748              .
749            </para>
750           
751            <programlisting>
752StringParameterType stringPT = new StringParameterType(255, null, true);
753PluginParameter one = new PluginParameter("one", "One", "First string", stringPT);
754PluginParameter two = new PluginParameter("two", "Two", "Second string", stringPT);
755// ... and so on
756            </programlisting>
757            <para>
758              The
759              <classname>ParameterType</classname>
760              is an abstract base class for several subclasses each implementing a
761              specific type of parameter. The list of subclasses may grow in the
762              future, but here are the most important ones currently implemented.
763            </para>
764            <note>
765              <para>
766                Most parameter types include support for supplying a predefined list
767                of options to select from. In that case the list will be displayed
768                as a drop-down list for the user, otherwise a free input field is
769                used.
770              </para>
771            </note>
772            <variablelist>
773              <varlistentry>
774                <term>
775                  <classname>StringParameterType</classname>
776                </term>
777                <listitem>
778                  <para>
779                    Asks for a string value. Includes an option for
780                    specifying the maximum length of the string.
781                  </para>
782                </listitem>
783              </varlistentry>
784              <varlistentry>
785                <term>
786                  <classname>FloatParameterType</classname>,
787                  <classname>DoubleParameterType</classname>,
788                  <classname>IntegerParameterType</classname>,
789                  <classname>LongParameterType</classname>
790                </term>
791                <listitem>
792                  <para>
793                    Asks for numerical values. Includes options for
794                    specifying a range (min/max) of allowed values.
795                  </para>
796                </listitem>
797              </varlistentry>
798              <varlistentry>
799                <term>
800                  <classname>BooleanParameterType</classname>
801                </term>
802                <listitem>
803                  <para>Asks for a boolean value.
804                  </para>
805                </listitem>
806              </varlistentry>
807              <varlistentry>
808                <term>
809                  <classname>DateParameterType</classname>
810                </term>
811                <listitem>
812                  <para>Asks for a date.
813                  </para>
814                </listitem>
815              </varlistentry>
816              <varlistentry>
817                <term>
818                  <classname>FileParameterType</classname>
819                </term>
820                <listitem>
821                  <para>Asks for a file item.
822                  </para>
823                </listitem>
824              </varlistentry>
825              <varlistentry>
826                <term>
827                  <classname>ItemParameterType</classname>
828                </term>
829                <listitem>
830                  <para>
831                    Asks for any other item. This parameter type requires
832                    that a list of options is supplied, except when the item
833                    type asked for matches the current GuiContext, in which
834                    case the currently selected item is used as the
835                    parameter value.
836                  </para>
837                </listitem>
838              </varlistentry>
839              <varlistentry>
840                <term>
841                  <classname>PathParameterType</classname>
842                </term>
843                <listitem>
844                  <para>
845                    Ask for a path to a file or directory. The path may be
846                    non-existing and should be used when a plugin needs an
847                    output destination, i.e., the file to export to, or a
848                    directory where the output files should be placed.
849                  </para>
850                </listitem>
851              </varlistentry>
852            </variablelist>
853            <para>
854              You can also create a
855              <classname>PluginParameter</classname>
856              with a null name and
857              <classname>ParameterType</classname>
858              . In that case, the core will not ask for input from the user, instead
859              it is used as a section header, allowing you to group parameters into
860              different sections which increase the readability of the input
861              parameters page.
862            </para>
863            <programlisting>
864PluginParameter firstSection = new PluginParameter(null, "First section", null, null);
865PluginParameter secondSection = new PluginParameter(null, "Second section", null, null);
866// ...
867
868parameters.add(firstSection);
869parameters.add(firstParameterInFirstSection);
870parameters.add(secondParameteInFirstSection);
871
872parameters.add(secondSection);
873parameters.add(firstParameterInSecondSection);
874parameters.add(secondParameteInSecondSection);
875            </programlisting>
876          </listitem>
877        </varlistentry>
878        <varlistentry>
879          <term>
880            <methodsynopsis language="java">
881              <modifier>public</modifier>
882              <void />
883              <methodname>configure</methodname>
884              <methodparam>
885                <type>GuiContext</type>
886                <parameter>context</parameter>
887              </methodparam>
888              <methodparam>
889                <type>Request</type>
890                <parameter>request</parameter>
891              </methodparam>
892              <methodparam>
893                <type>Response</type>
894                <parameter>response</parameter>
895              </methodparam>
896            </methodsynopsis>
897          </term>
898          <listitem>
899            <para>
900              Sends parameter values entered by the user for processing by the plugin.
901              Typically the plugin should validate that the parameter values are
902              correct and then store them in database.
903            </para>
904            <para>
905              No validation is done by the core, except converting the input to the
906              correct object type, i.e. if the parameter asked for a
907              <classname>Float</classname>
908              the input string is parsed and converted to a Float. If you have
909              extended the
910              <classname>AbstractPlugin</classname>
911              class it is very easy to validate the parameters using it's
912              <methodname>validateRequestParameters</methodname>
913              () method. This method takes the same list of
914              <classname>PluginParameter</classname>
915              's used in the
916              <classname>RequestInformation</classname>
917              object and uses that information for validation. It returns null or a
918              list of
919              <exceptionname>Throwable</exceptionname>
920              .
921            </para>
922            <para>
923              When the parameters have been validated they need to be stored. Once
924              again, it is very easy if you use one of the
925              <classname>AbstractPlugin.</classname>
926              <methodname>storeValue</methodname>
927              () or
928              <classname>AbstractPlugin.</classname>
929              <methodname>storeValues</methodname>
930              () methods.
931            </para>
932            <para>
933              The configure method works much like the
934              <classname>Plugin.</classname>
935              <methodname>run</methodname>
936              () method. It must return the result in the
937              <classname>Response</classname>
938              object, i.e. it shouldn't trow any exceptions.
939            </para>
940            <example id="net.sf.basedb.core.plugin.InteractivePlugin.configure">
941              <title>
942                Configuration implementation building on the examples above
943              </title>
944              <programlisting>
945public void configure(GuiContext context, Request request, Response response)
946{
947   String command = request.getCommand();
948   try
949   {
950      if (command.equals(Request.COMMAND_CONFIGURE_PLUGIN))
951      {
952         // TODO
953      }
954      else if (command.equals(Request.COMMAND_CONFIGURE_JOB))
955      {
956         // Validate user input
957         List&lt;Throwable&gt; errors =
958            validateRequestParameters(getConfigureJob().getParameters(), request);
959         if (errors != null)
960         {
961            response.setError(errors.size() +
962               " invalid parameter(s) were found in the request", errors);
963            return;
964         }
965         
966         // Store user input
967         storeValue(job, request, fileParameter);
968         storeValue(job, request, updateExistingParameter);
969         
970         // We are happy and done
971         response.setDone("Job configuration complete", Job.ExecutionTime.SHORT);
972         // TODO - check file size to make a better estimate of execution time
973      }
974   }
975   catch (Throwable ex)
976   {
977      response.setError(ex.getMessage(), Arrays.asList(ex));
978   }
979}
980              </programlisting>
981            </example>
982            <para>
983              Note that the
984              <methodname>setDone</methodname>
985              () has a second parameter
986              <classname>Job.ExecutionTime</classname>
987              . It is an indication about how long time it will take to execute the
988              plugin. This is of interest for job queue managers which probably
989              doesn't want to start too many long-running jobs at the same time
990              blocking the entire system. Please try to use this parameter wisely and
991              not use the
992              <constant>SHORT</constant>
993              value out of old habit all the time.
994            </para>
995            <para>
996              The response also has a
997              <methodname>setContinue</methodname>
998              () method which tells the core that the plugin needs more parameters,
999              i.e. the core will then call
1000              <methodname>getRequestInformation</methodname>
1001              () again with the new command, let the user enter values, and the call
1002              <methodname>configure</methodname>
1003              () with the new values. This process is repeated until the plugin
1004              reports that it is done or an error occurs.
1005            </para>
1006            <para>
1007              An important note is that during this iteration it is the same instance
1008              of the plugin that is used. However, no parameter values are stored in
1009              the database until
1010              <methodname>setDone</methodname>
1011              () is called. Then, the plugin instance is usually discarded. The
1012              execution of the plugin happens in a new instance and maybe on a
1013              different server.
1014            </para>
1015            <tip>
1016                <para>
1017                  You don't have to store all values the plugin asked for in the
1018                  first place. You may even choose to store different values than
1019                  those that were entered. For example, you might ask for the mass
1020                  and height of a person and then only store the body mass index,
1021                  which is calculated from those values.
1022                </para>
1023            </tip>
1024          </listitem>
1025        </varlistentry>
1026      </variablelist>
1027    </sect2>
1028
1029  </sect1>
1030
1031  <sect1 id="plugin_developer.organize">
1032    <title>How to organize your plugin project</title>
1033    <para>
1034      Here is a simple example of how you might organize your project using ant (
1035      <ulink url="http://ant.apache.org">http://ant.apache.org</ulink>
1036      ) as the build tool. This is just a recommendation that we have found to be working
1037      well. You may choose to do it another way.
1038    </para>
1039   
1040    <sect2 id="plugin_developer.organize.layout">
1041      <title>Directory layout</title>
1042      <para>
1043        <literallayout>
1044          <filename class="directory"><replaceable>pluginname</replaceable>/</filename>
1045          <filename class="directory"><replaceable>pluginname</replaceable>/bin/</filename>
1046          <filename class="directory"><replaceable>pluginname</replaceable>/lib/</filename>
1047          <filename class="directory"><replaceable>pluginname</replaceable>/src/<replaceable>org/company</replaceable>/</filename>
1048        </literallayout>
1049        The
1050        <filename class="directory">bin/</filename>
1051        directory is empty to start with. It will contain the compiled code. The
1052        <filename class="directory">lib/</filename>
1053        directory contains the JAR files your plugin uses (including the
1054        <filename>BASE2Core.jar</filename>
1055        ). The
1056        <filename class="directory">src/</filename>
1057        directory contains your source code.
1058      </para>
1059    </sect2>
1060   
1061    <sect2 id="plugin_developer.organize.build">
1062      <title>Ant build file</title>
1063      <para>
1064        In the root of your directory, create the build file:
1065        <filename>build.xml</filename>
1066        . Here is an example that will compile your plugin and put it in a JAR file.
1067      </para>
1068      <example id="plugin_developer.organize.build.file">
1069        <title>A simple build file</title>
1070        <programlisting>
1071&lt;?xml version="1.0" encoding="UTF-8"?&gt;
1072&lt;project
1073   name="MyPlugin"
1074   default="build.plugin"
1075   basedir="."
1076   &gt;
1077
1078   &lt;!-- variables used --&gt;
1079   &lt;property name="plugin.name" value="MyPlugin" /&gt;
1080   &lt;property name="src" value="src" /&gt;
1081   &lt;property name="bin" value="bin" /&gt;
1082
1083   &lt;!-- set up classpath for compiling --&gt;
1084   &lt;path id="classpath"&gt;
1085      &lt;fileset dir="lib"&gt;
1086         &lt;include name="**/*.jar"/&gt;
1087      &lt;/fileset&gt;
1088   &lt;/path&gt;
1089
1090   
1091   &lt;!-- main target --&gt;
1092   &lt;target
1093      name="build.plugin" 
1094      description="Compiles the plugin and put in jar"
1095      &gt;
1096      &lt;javac
1097         encoding="ISO-8859-1"
1098         srcdir="${src}"
1099         destdir="${bin}"
1100         classpathref="classpath"&gt;
1101      &lt;/javac&gt;
1102      &lt;jar
1103         jarfile="${plugin.name}.jar"
1104         basedir="bin"
1105         manifest="MANIFEST.MF"
1106         &gt;
1107      &lt;/jar&gt;
1108
1109    &lt;/target&gt;
1110&lt;/project&gt; 
1111        </programlisting>
1112      </example>
1113      <para>
1114        If your plugin depends on other JAR files than the
1115        <filename>Base2Core.jar</filename>
1116        you should list them in the
1117        <filename>MANIFEST.MF</filename>
1118        file. Otherwise you should remove the manifest attribute of the jar tag in the build
1119        file.
1120      </para>
1121        <programlisting>
1122Manifest-Version: 1.0
1123Class-Path: OtherJar.jar ASecondJar.jar
1124        </programlisting>
1125    </sect2>
1126     
1127    <sect2>
1128      <title>Building the plugin</title>
1129      <para>
1130        Compile the plugin simply by typing
1131        <command>ant</command>
1132        in the console window. If all went well the
1133        <filename>MyPlugin.jar</filename>
1134        will be created in the same directory.
1135      </para>
1136      <para>
1137        To install the plugin copy the JAR file to the server including the dependent JAR
1138        files (if any). Place all files together in the same directory. Then follow the
1139        instructions in chapter
1140        <link linkend="plugin_installation" endterm="plugin_installation.title"></link>
1141        .
1142      </para>
1143    </sect2>
1144  </sect1>
1145
1146  <sect1 id="plugin_developer.customJSP">
1147    <title>Using a custom JSP page for parameter input</title>
1148    <para>
1149      This is an advanced option for plugins that require a different interface for specifying
1150      plugin parameters than the default list showing each parameter at a time. This feature
1151      is used by setting the RequestInformation.getJspPage() property when construction the
1152      request information object. If this property has a non-null value, the web client will
1153      send the browser to the specified JSP page instead of to the generic parameter input
1154      page.
1155    </para>
1156    <para>
1157      When setting the JSP page you should not specify any path information. The web client
1158      has a special location for these JSP pages, generated from the package name of your
1159      plugin and the returned values. If the plugin is located in the package
1160      <classname><replaceable>org.company</replaceable></classname>
1161      the JSP page must be located in
1162      <filename class="directory">
1163        <replaceable>www-root</replaceable>
1164        /plugins/
1165        <replaceable>org/company</replaceable>
1166        /
1167      </filename>
1168      . Please note that the browser still thinks that it is showing the regular page at the
1169      usual location:
1170      <filename class="directory"><replaceable>www-root</replaceable>/common/plugin/index.jsp</filename>
1171      , so all links in your JSP page should be relative to that directory.
1172    </para>
1173    <para>
1174      Even if you use your own JSP page we recommend that you use the built-in facility for
1175      passing the parameters back to the plugin. For this to work you must:
1176    </para>
1177      <itemizedlist spacing="compact">
1178        <listitem>
1179          <simpara>Generate the list of <classname>PluginParameter</classname> objects as usual</simpara>
1180        </listitem>
1181        <listitem>
1182          <simpara>
1183            Name all your input fields like:
1184            <parameter>
1185              parameter:
1186              <replaceable>name-of-parameter</replaceable>
1187            </parameter>
1188          </simpara>
1189          <programlisting>
1190// Plugin generate PluginParameter
1191StringParameterType stringPT = new StringParameterType(255, null, true);
1192PluginParameter one = new PluginParameter("one", "One", "First string", stringPT);
1193PluginParameter two = new PluginParameter("two", "Two", "Second string", stringPT);
1194
1195// JSP should name fields as:
1196First string: &lt;input type="text" name="parameter:one"&gt;&lt;br&gt;
1197Second string: &lt;input type="text" name="parameter:two"&gt;
1198          </programlisting>
1199        </listitem>
1200        <listitem>
1201        <simpara>
1202          Send the form to
1203          <filename>index.jsp</filename>
1204          with some parameters
1205        </simpara>
1206        <programlisting>
1207&lt;form action="index.jsp" method="post"&gt;
1208&lt;input type="hidden" name="ID" value="&lt;%=ID%&gt;"&gt;
1209&lt;input type="hidden" name="cmd" value="SetParameters"&gt;
1210...
1211&lt;/form&gt;
1212          </programlisting>
1213        <para>
1214          In your JSP page you will probably need to access some information like the
1215          <classname>SessionControl</classname>
1216          and possible even the
1217          <classname>RequestInformation</classname>
1218          object created by your plugin.
1219        </para>
1220        <programlisting>
1221// Get session control and it's ID (required to post to index.jsp)
1222final SessionControl sc = Base.getExistingSessionControl(pageContext, true);
1223final String ID = sc.getId();
1224
1225// Get information about the current request to the plugin
1226PluginConfigurationRequest pcRequest =
1227   (PluginConfigurationRequest)sc.getSessionSetting("plugin.configure.request");
1228PluginDefinition plugin =
1229   (PluginDefinition)sc.getSessionSetting("plugin.configure.plugin");
1230PluginConfiguration pluginConfig =
1231   (PluginConfiguration)sc.getSessionSetting("plugin.configure.config");
1232PluginDefinition job =
1233   (PluginDefinition)sc.getSessionSetting("plugin.configure.job");
1234RequestInformation ri = pcRequest.getRequestInformation();
1235        </programlisting>
1236        </listitem>
1237      </itemizedlist>
1238  </sect1>
1239
1240</chapter>
Note: See TracBrowser for help on using the repository browser.