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

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

fixed linkes
fixed some methodname tags

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 42.5 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 3175 2007-03-08 09:57:25Z 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              <constant>Type.LIST</constant>
462              or
463              <constant>Type.ITEM</constant>
464              .
465            </para>
466            <para>
467              For example, the
468              <varname>GuiContext</varname>
469              <literal>= (</literal>
470              <constant>Item.REPORTER</constant>
471              <literal>,</literal>
472              <constant>Type.LIST</constant>
473              <literal>)</literal>
474              tells a client application that this plugin can be plugged in whenever a
475              list of reporters is displayed. The
476              <varname>GuiContext</varname>
477              = (
478              <constant>Item.REPORTER</constant>
479              ,
480              <constant>Type.ITEM</constant>
481              ) tells a client application that this plugin can be plugged in whenever
482              a single reporter is displayed. The first case may be appropriate for a
483              plugin that imports or exports reporters. The second case may be used by
484              a plugin that updates the reporter information from an external source
485              (well, it may make sense to use this in the list case as well).
486            </para>
487            <para>
488              The returned information is copied by the core at installation time to
489              make it easy to ask for all plugins for a certain
490              <classname>GuiContext</classname>
491              .
492            </para>
493            <para>
494              A typical implementation creates a static unmodifiable
495              <classname>Set</classname>
496              which is returned by this method. It is important that the returned set
497              can't be modified, since it may be a security issue if a bad behaving
498              client application does that.
499            </para>
500            <example id="net.sf.basedb.core.plugin.InteractivePlugin.getGuiContexts">
501              <title>
502                A typical implementation of
503                <methodname>getGuiContexts</methodname>
504              </title>
505              <programlisting>
506// From the net.sf.basedb.plugins.RawDataFlatFileImporter plugin
507private static final Set&lt;GuiContext&gt; guiContexts =
508   Collections.singleton(new GuiContext(Item.RAWBIOASSAY, GuiContext.Type.ITEM));
509
510public Set&lt;GuiContext&gt; <methodname>getGuiContexts</methodname>()
511{
512   return <returnvalue>guiContexts</returnvalue>;
513}
514              </programlisting>
515            </example>
516          </listitem>
517        </varlistentry>
518        <varlistentry>
519          <term>
520            <methodsynopsis language="java">
521              <modifier>public</modifier>
522              <type>String</type>
523              <methodname>isInContext</methodname>
524              <methodparam>
525                <type>GuiContext</type>
526                <parameter>context</parameter>
527              </methodparam>
528              <methodparam>
529                <type>Object</type>
530                <parameter>item</parameter>
531              </methodparam>
532            </methodsynopsis>
533          </term>
534          <listitem>
535            <para>
536              This method is called to check if a particular item is usable for the
537              plugin, when the context type is
538              <constant>Type.ITEM</constant>
539              , i.e. the user has selected a specific sample and the the client
540              application is now displaying information about that sample. Thus, our
541              <varname>GuiContext</varname>
542              = (
543              <constant>Item.SAMPLE</constant>
544              ,
545              <constant>Type.ITEM</constant>
546              ). Now, the client application asks for a list of plugins supporting
547              this context and for each one in the list calls this method with the
548              current sample as the item parameter. The plugin should answer if it can
549              do whatever it is supposed to do by returning null or a string
550              containing a message why it can't.
551            </para>
552            <para>
553              Here is a real example from the
554              <classname>RawDataFlatFileImporter</classname>
555              plugin which imports raw data to a
556              <classname>RawBioAssay</classname>
557              . Thus,
558              <varname>GuiContext</varname>
559              = (
560              <constant>Item.RAWBIOASSAY</constant>
561              ,
562              <constant>Type.ITEM</constant>
563              ), but the plugin can only import data if there isn't any already, and
564              if the raw bioassay has the same raw data type as the plugin has been
565              configured for.
566            </para>
567            <example id="net.sf.basedb.core.plugin.InteractivePlugin.isInContext">
568              <title>
569                A simple implementation of
570                <methodname>isInContext</methodname>
571              </title>
572              <programlisting>
573/**
574   Returns null if the item is a {@link RawBioAssay} of the correct
575   {@link RawDataType} and doesn't already have spots.
576*/
577public String isInContext(GuiContext context, Object item)
578{
579   String message = null;
580   if (item == null)
581   {
582      message = "The object is null";
583   }
584   else if (!(item instanceof RawBioAssay))
585   {
586      message = "The object is not a RawBioAssay: " + item;
587   }
588   else
589   {
590      RawBioAssay rba = (RawBioAssay)item;
591      String rawDataType = (String)configuration.getValue("rawDataType");
592      if (rba.getSpots() > 0)
593      {
594         message = "The raw bioassay already has spots: " + rba.getName();
595      }
596      else if (!rba.getRawDataType().getId().equals(rawDataType))
597      {
598         message = "Unsupported raw data type: " + rba.getRawDataType().getName();
599      }
600   }
601   return message;   
602}
603              </programlisting>
604            </example>
605          </listitem>
606        </varlistentry>
607        <varlistentry>
608          <term>
609            <methodsynopsis language="java">
610              <modifier>public</modifier>
611              <type>RequestInformation</type>
612              <methodname>getRequestInformation</methodname>
613              <methodparam>
614                <type>GuiContext</type>
615                <parameter>context</parameter>
616              </methodparam>
617              <methodparam>
618                <type>String</type>
619                <parameter>command</parameter>
620              </methodparam>
621              <exceptionname>BaseException</exceptionname>
622            </methodsynopsis>
623          </term>
624          <listitem>
625            <para>
626              Ask the plugin for parameters that needs to be entered by the user. The
627              <classname>GuiContext</classname>
628              parameter is one of the contexts returned by the
629              <methodname>getGuiContexts</methodname>
630              method. The command is string telling the plugin what command was
631              executed. There are two predefined commands but as you will see the
632              plugin may define it's own commands. The two predefined commands are
633              defined in the
634              <classname>net.sf.basedb.core.plugin.Request</classname>
635              class.
636              <variablelist>
637                <varlistentry>
638                  <term>
639                    <constant>Request.COMMAND_CONFIGURE_PLUGIN</constant>
640                  </term>
641                  <listitem>
642                    <para>
643                      Used when an administrator is initiating a configuration
644                      of the plugin.
645                    </para>
646                  </listitem>
647                </varlistentry>
648                <varlistentry>
649                  <term>
650                    <constant>Request.COMMAND_CONFIGURE_JOB</constant>
651                  </term>
652                  <listitem>
653                    <para>
654                      Used when a user has selected the plugin for running a
655                      job.
656                    </para>
657                  </listitem>
658                </varlistentry>
659              </variablelist>
660              Given this information the plugin must return a
661              <classname>RequestInformation</classname>
662              object. This is simply a title, a description and a list of parameters.
663              Usually the title will end up as the input form title and the
664              description as a help text for the entire form. Do not put information
665              about the individual parameters in this description, since each
666              parameter has a description of their own.
667            </para>
668            <example id="net.sf.basedb.core.plugin.InteractivePlugin.getRequestInformation_1">
669              <title>
670                When running an import plugin it needs to ask for the file to import
671                from and if existing items should be updated or not
672              </title>
673              <programlisting>
674// The complete request information
675private RequestInformation configure Job;
676
677// The parameter that asks for a file to import from
678private PluginParameter&lt;File&gt; file Parameter;
679
680// The parameter that asks if existing items should be updated or not
681private PluginParameter&lt;Boolean&gt; updateExistingParameter;
682
683public RequestInformation getRequestInformation(GuiContext context, String command)
684   throws BaseException
685{
686   RequestInformation requestInformation = null;
687   if (command.equals(Request.COMMAND_CONFIGURE_PLUGIN))
688   {
689      requestInformation = getConfigurePlugin();
690   }
691   else if (command.equals(Request.COMMAND_CONFIGURE_JOB))
692   {
693      requestInformation = getConfigureJob();
694   }
695   return requestInformation;
696}
697
698/**
699   Get (and build) the request information for starting a job.
700*/
701private RequestInformation getConfigureJob()
702{
703   if (configureJob == null)
704   {
705      fileParameter = new PluginParameter&lt;File&gt;(
706         "file",
707         "File",
708         "The file to import the data from",
709         new FileParameterType(null, true, 1)
710      );
711     
712      updateExistingParameter = new PluginParameter&lt;Boolean&gt;(
713         "updateExisting",
714         "Update existing items",
715         "If this option is selected, already existing items will be updated " +
716         " with the information in the file. If this option isn't selected " +
717         " existing items are left untouched.",
718         new BooleanParameterType(false, true)
719      );
720
721      List&lt;PluginParameter&lt;?&gt;&gt; parameters =
722         new ArrayList&lt;PluginParameter&lt;?&gt;&gt;(2);
723      parameters.add(fileParameter);
724      parameters.add(updateExistingParameter);
725     
726      configureJob = new RequestInformation
727      (
728         Request.COMMAND_CONFIGURE_JOB,
729         "Select a file to import items from",
730         "Description",
731         parameters
732      );
733   }
734   return configureJob;
735}
736              </programlisting>
737            </example>
738            <para>
739              As you can see it takes some code to put together a
740              <classname>RequestInformation</classname>
741              object. For each parameter needed you need one
742              <classname>PluginParameter</classname>
743              object and one
744              <classname>ParameterType</classname>
745              object. Actually, a
746              <classname>ParameterType</classname>
747              can be reused for more than one
748              <classname>PluginParameter</classname>
749              .
750            </para>
751           
752            <programlisting>
753StringParameterType stringPT = new StringParameterType(255, null, true);
754PluginParameter one = new PluginParameter("one", "One", "First string", stringPT);
755PluginParameter two = new PluginParameter("two", "Two", "Second string", stringPT);
756// ... and so on
757            </programlisting>
758            <para>
759              The
760              <classname>ParameterType</classname>
761              is an abstract base class for several subclasses each implementing a
762              specific type of parameter. The list of subclasses may grow in the
763              future, but here are the most important ones currently implemented.
764            </para>
765            <note>
766              <para>
767                Most parameter types include support for supplying a predefined list
768                of options to select from. In that case the list will be displayed
769                as a drop-down list for the user, otherwise a free input field is
770                used.
771              </para>
772            </note>
773            <variablelist>
774              <varlistentry>
775                <term>
776                  <classname>StringParameterType</classname>
777                </term>
778                <listitem>
779                  <para>
780                    Asks for a string value. Includes an option for
781                    specifying the maximum length of the string.
782                  </para>
783                </listitem>
784              </varlistentry>
785              <varlistentry>
786                <term>
787                  <classname>FloatParameterType</classname>,
788                  <classname>DoubleParameterType</classname>,
789                  <classname>IntegerParameterType</classname>,
790                  <classname>LongParameterType</classname>
791                </term>
792                <listitem>
793                  <para>
794                    Asks for numerical values. Includes options for
795                    specifying a range (min/max) of allowed values.
796                  </para>
797                </listitem>
798              </varlistentry>
799              <varlistentry>
800                <term>
801                  <classname>BooleanParameterType</classname>
802                </term>
803                <listitem>
804                  <para>Asks for a boolean value.
805                  </para>
806                </listitem>
807              </varlistentry>
808              <varlistentry>
809                <term>
810                  <classname>DateParameterType</classname>
811                </term>
812                <listitem>
813                  <para>Asks for a date.
814                  </para>
815                </listitem>
816              </varlistentry>
817              <varlistentry>
818                <term>
819                  <classname>FileParameterType</classname>
820                </term>
821                <listitem>
822                  <para>Asks for a file item.
823                  </para>
824                </listitem>
825              </varlistentry>
826              <varlistentry>
827                <term>
828                  <classname>ItemParameterType</classname>
829                </term>
830                <listitem>
831                  <para>
832                    Asks for any other item. This parameter type requires
833                    that a list of options is supplied, except when the item
834                    type asked for matches the current GuiContext, in which
835                    case the currently selected item is used as the
836                    parameter value.
837                  </para>
838                </listitem>
839              </varlistentry>
840              <varlistentry>
841                <term>
842                  <classname>PathParameterType</classname>
843                </term>
844                <listitem>
845                  <para>
846                    Ask for a path to a file or directory. The path may be
847                    non-existing and should be used when a plugin needs an
848                    output destination, i.e., the file to export to, or a
849                    directory where the output files should be placed.
850                  </para>
851                </listitem>
852              </varlistentry>
853            </variablelist>
854            <para>
855              You can also create a
856              <classname>PluginParameter</classname>
857              with a null name and
858              <classname>ParameterType</classname>
859              . In that case, the core will not ask for input from the user, instead
860              it is used as a section header, allowing you to group parameters into
861              different sections which increase the readability of the input
862              parameters page.
863            </para>
864            <programlisting>
865PluginParameter firstSection = new PluginParameter(null, "First section", null, null);
866PluginParameter secondSection = new PluginParameter(null, "Second section", null, null);
867// ...
868
869parameters.add(firstSection);
870parameters.add(firstParameterInFirstSection);
871parameters.add(secondParameteInFirstSection);
872
873parameters.add(secondSection);
874parameters.add(firstParameterInSecondSection);
875parameters.add(secondParameteInSecondSection);
876            </programlisting>
877          </listitem>
878        </varlistentry>
879        <varlistentry>
880          <term>
881            <methodsynopsis language="java">
882              <modifier>public</modifier>
883              <void />
884              <methodname>configure</methodname>
885              <methodparam>
886                <type>GuiContext</type>
887                <parameter>context</parameter>
888              </methodparam>
889              <methodparam>
890                <type>Request</type>
891                <parameter>request</parameter>
892              </methodparam>
893              <methodparam>
894                <type>Response</type>
895                <parameter>response</parameter>
896              </methodparam>
897            </methodsynopsis>
898          </term>
899          <listitem>
900            <para>
901              Sends parameter values entered by the user for processing by the plugin.
902              Typically the plugin should validate that the parameter values are
903              correct and then store them in database.
904            </para>
905            <para>
906              No validation is done by the core, except converting the input to the
907              correct object type, i.e. if the parameter asked for a
908              <classname>Float</classname>
909              the input string is parsed and converted to a Float. If you have
910              extended the
911              <classname>AbstractPlugin</classname>
912              class it is very easy to validate the parameters using it's
913              <methodname>validateRequestParameters()</methodname>
914              method. This method takes the same list of
915              <classname>PluginParameter</classname>
916              's used in the
917              <classname>RequestInformation</classname>
918              object and uses that information for validation. It returns null or a
919              list of
920              <exceptionname>Throwable</exceptionname>
921              .
922            </para>
923            <para>
924              When the parameters have been validated they need to be stored. Once
925              again, it is very easy if you use one of the
926              <methodname>AbstractPlugin.storeValue()</methodname>
927              or
928              <methodname>AbstractPlugin.storeValues()</methodname>
929              methods.
930            </para>
931            <para>
932              The configure method works much like the
933              <methodname>Plugin.run()</methodname>
934              method. It must return the result in the
935              <classname>Response</classname>
936              object, i.e. it shouldn't trow any exceptions.
937            </para>
938            <example id="net.sf.basedb.core.plugin.InteractivePlugin.configure">
939              <title>
940                Configuration implementation building on the examples above
941              </title>
942              <programlisting>
943public void configure(GuiContext context, Request request, Response response)
944{
945   String command = request.getCommand();
946   try
947   {
948      if (command.equals(Request.COMMAND_CONFIGURE_PLUGIN))
949      {
950         // TODO
951      }
952      else if (command.equals(Request.COMMAND_CONFIGURE_JOB))
953      {
954         // Validate user input
955         List&lt;Throwable&gt; errors =
956            validateRequestParameters(getConfigureJob().getParameters(), request);
957         if (errors != null)
958         {
959            response.setError(errors.size() +
960               " invalid parameter(s) were found in the request", errors);
961            return;
962         }
963         
964         // Store user input
965         storeValue(job, request, fileParameter);
966         storeValue(job, request, updateExistingParameter);
967         
968         // We are happy and done
969         response.setDone("Job configuration complete", Job.ExecutionTime.SHORT);
970         // TODO - check file size to make a better estimate of execution time
971      }
972   }
973   catch (Throwable ex)
974   {
975      response.setError(ex.getMessage(), Arrays.asList(ex));
976   }
977}
978              </programlisting>
979            </example>
980            <para>
981              Note that the
982              <methodname>setDone()</methodname>
983              has a second parameter
984              <classname>Job.ExecutionTime</classname>
985              . It is an indication about how long time it will take to execute the
986              plugin. This is of interest for job queue managers which probably
987              doesn't want to start too many long-running jobs at the same time
988              blocking the entire system. Please try to use this parameter wisely and
989              not use the
990              <constant>Job.ExecutionTime.SHORT</constant>
991              value out of old habit all the time.
992            </para>
993            <para>
994              The response also has a
995              <methodname>setContinue()</methodname>
996              method which tells the core that the plugin needs more parameters,
997              i.e. the core will then call
998              <methodname>getRequestInformation()</methodname>
999              again with the new command, let the user enter values, and the call
1000              <methodname>configure()</methodname>
1001              with the new values. This process is repeated until the plugin
1002              reports that it is done or an error occurs.
1003            </para>
1004            <para>
1005              An important note is that during this iteration it is the same instance
1006              of the plugin that is used. However, no parameter values are stored in
1007              the database until
1008              <methodname>setDone()</methodname>
1009              is called. Then, the plugin instance is usually discarded. The execution
1010              of the plugin happens in a new instance and maybe on a different server.
1011            </para>
1012            <tip>
1013                <para>
1014                  You don't have to store all values the plugin asked for in the
1015                  first place. You may even choose to store different values than
1016                  those that were entered. For example, you might ask for the mass
1017                  and height of a person and then only store the body mass index,
1018                  which is calculated from those values.
1019                </para>
1020            </tip>
1021          </listitem>
1022        </varlistentry>
1023      </variablelist>
1024    </sect2>
1025
1026  </sect1>
1027
1028  <sect1 id="plugin_developer.organize">
1029    <title>How to organize your plugin project</title>
1030    <para>
1031      Here is a simple example of how you might organize your project using ant (
1032      <ulink url="http://ant.apache.org">http://ant.apache.org</ulink>
1033      ) as the build tool. This is just a recommendation that we have found to be working
1034      well. You may choose to do it another way.
1035    </para>
1036   
1037    <sect2 id="plugin_developer.organize.layout">
1038      <title>Directory layout</title>
1039      <para>
1040        <literallayout>
1041          <filename class="directory"><replaceable>pluginname</replaceable>/</filename>
1042          <filename class="directory"><replaceable>pluginname</replaceable>/bin/</filename>
1043          <filename class="directory"><replaceable>pluginname</replaceable>/lib/</filename>
1044          <filename class="directory"><replaceable>pluginname</replaceable>/src/<replaceable>org/company</replaceable>/</filename>
1045        </literallayout>
1046        The
1047        <filename class="directory">bin/</filename>
1048        directory is empty to start with. It will contain the compiled code. The
1049        <filename class="directory">lib/</filename>
1050        directory contains the JAR files your plugin uses (including the
1051        <filename>BASE2Core.jar</filename>
1052        ). The
1053        <filename class="directory">src/</filename>
1054        directory contains your source code.
1055      </para>
1056    </sect2>
1057   
1058    <sect2 id="plugin_developer.organize.ant">
1059      <title>Ant build file</title>
1060      <para>
1061        In the root of your directory, create the build file:
1062        <filename>build.xml</filename>
1063        . Here is an example that will compile your plugin and put it in a JAR file.
1064      </para>
1065      <example id="plugin_developer.organize.build.file">
1066        <title>A simple build file</title>
1067        <programlisting>
1068&lt;?xml version="1.0" encoding="UTF-8"?&gt;
1069&lt;project
1070   name="MyPlugin"
1071   default="build.plugin"
1072   basedir="."
1073   &gt;
1074
1075   &lt;!-- variables used --&gt;
1076   &lt;property name="plugin.name" value="MyPlugin" /&gt;
1077   &lt;property name="src" value="src" /&gt;
1078   &lt;property name="bin" value="bin" /&gt;
1079
1080   &lt;!-- set up classpath for compiling --&gt;
1081   &lt;path id="classpath"&gt;
1082      &lt;fileset dir="lib"&gt;
1083         &lt;include name="**/*.jar"/&gt;
1084      &lt;/fileset&gt;
1085   &lt;/path&gt;
1086
1087   
1088   &lt;!-- main target --&gt;
1089   &lt;target
1090      name="build.plugin" 
1091      description="Compiles the plugin and put in jar"
1092      &gt;
1093      &lt;javac
1094         encoding="ISO-8859-1"
1095         srcdir="${src}"
1096         destdir="${bin}"
1097         classpathref="classpath"&gt;
1098      &lt;/javac&gt;
1099      &lt;jar
1100         jarfile="${plugin.name}.jar"
1101         basedir="bin"
1102         manifest="MANIFEST.MF"
1103         &gt;
1104      &lt;/jar&gt;
1105
1106    &lt;/target&gt;
1107&lt;/project&gt; 
1108        </programlisting>
1109      </example>
1110      <para>
1111        If your plugin depends on other JAR files than the
1112        <filename>Base2Core.jar</filename>
1113        you should list them in the
1114        <filename>MANIFEST.MF</filename>
1115        file. Otherwise you should remove the manifest attribute of the jar tag in the build
1116        file.
1117      </para>
1118        <programlisting>
1119Manifest-Version: 1.0
1120Class-Path: OtherJar.jar ASecondJar.jar
1121        </programlisting>
1122    </sect2>
1123     
1124    <sect2 id="plugin_developer.organize.build">
1125      <title>Building the plugin</title>
1126      <para>
1127        Compile the plugin simply by typing
1128        <command>ant</command>
1129        in the console window. If all went well the
1130        <filename>MyPlugin.jar</filename>
1131        will be created in the same directory.
1132      </para>
1133      <para>
1134        To install the plugin copy the JAR file to the server including the dependent JAR
1135        files (if any). Place all files together in the same directory. Then follow the
1136        instructions in chapter
1137        <xref linkend="plugin_installation" />
1138        .
1139      </para>
1140    </sect2>
1141  </sect1>
1142
1143  <sect1 id="plugin_developer.customJSP">
1144    <title>Using a custom JSP page for parameter input</title>
1145    <para>
1146      This is an advanced option for plugins that require a different interface for specifying
1147      plugin parameters than the default list showing each parameter at a time. This feature
1148      is used by setting the RequestInformation.getJspPage() property when construction the
1149      request information object. If this property has a non-null value, the web client will
1150      send the browser to the specified JSP page instead of to the generic parameter input
1151      page.
1152    </para>
1153    <para>
1154      When setting the JSP page you should not specify any path information. The web client
1155      has a special location for these JSP pages, generated from the package name of your
1156      plugin and the returned values. If the plugin is located in the package
1157      <classname><replaceable>org.company</replaceable></classname>
1158      the JSP page must be located in
1159      <filename class="directory">
1160        <replaceable>www-root</replaceable>
1161        /plugins/
1162        <replaceable>org/company</replaceable>
1163        /
1164      </filename>
1165      . Please note that the browser still thinks that it is showing the regular page at the
1166      usual location:
1167      <filename class="directory"><replaceable>www-root</replaceable>/common/plugin/index.jsp</filename>
1168      , so all links in your JSP page should be relative to that directory.
1169    </para>
1170    <para>
1171      Even if you use your own JSP page we recommend that you use the built-in facility for
1172      passing the parameters back to the plugin. For this to work you must:
1173    </para>
1174      <itemizedlist spacing="compact">
1175        <listitem>
1176          <simpara>Generate the list of <classname>PluginParameter</classname> objects as usual</simpara>
1177        </listitem>
1178        <listitem>
1179          <simpara>
1180            Name all your input fields like:
1181            <parameter>
1182              parameter:
1183              <replaceable>name-of-parameter</replaceable>
1184            </parameter>
1185          </simpara>
1186          <programlisting>
1187// Plugin generate PluginParameter
1188StringParameterType stringPT = new StringParameterType(255, null, true);
1189PluginParameter one = new PluginParameter("one", "One", "First string", stringPT);
1190PluginParameter two = new PluginParameter("two", "Two", "Second string", stringPT);
1191
1192// JSP should name fields as:
1193First string: &lt;input type="text" name="parameter:one"&gt;&lt;br&gt;
1194Second string: &lt;input type="text" name="parameter:two"&gt;
1195          </programlisting>
1196        </listitem>
1197        <listitem>
1198        <simpara>
1199          Send the form to
1200          <filename>index.jsp</filename>
1201          with some parameters
1202        </simpara>
1203        <programlisting>
1204&lt;form action="index.jsp" method="post"&gt;
1205&lt;input type="hidden" name="ID" value="&lt;%=ID%&gt;"&gt;
1206&lt;input type="hidden" name="cmd" value="SetParameters"&gt;
1207...
1208&lt;/form&gt;
1209          </programlisting>
1210        <para>
1211          In your JSP page you will probably need to access some information like the
1212          <classname>SessionControl</classname>
1213          and possible even the
1214          <classname>RequestInformation</classname>
1215          object created by your plugin.
1216        </para>
1217        <programlisting>
1218// Get session control and it's ID (required to post to index.jsp)
1219final SessionControl sc = Base.getExistingSessionControl(pageContext, true);
1220final String ID = sc.getId();
1221
1222// Get information about the current request to the plugin
1223PluginConfigurationRequest pcRequest =
1224   (PluginConfigurationRequest)sc.getSessionSetting("plugin.configure.request");
1225PluginDefinition plugin =
1226   (PluginDefinition)sc.getSessionSetting("plugin.configure.plugin");
1227PluginConfiguration pluginConfig =
1228   (PluginConfiguration)sc.getSessionSetting("plugin.configure.config");
1229PluginDefinition job =
1230   (PluginDefinition)sc.getSessionSetting("plugin.configure.job");
1231RequestInformation ri = pcRequest.getRequestInformation();
1232        </programlisting>
1233        </listitem>
1234      </itemizedlist>
1235  </sect1>
1236
1237  <sect1 id="plugin_developer.import_plugins">
1238    <title id="plugin_developer.import_plugins.title">Plugins for importing data</title>
1239    <para></para>
1240  </sect1>
1241
1242</chapter>
Note: See TracBrowser for help on using the repository browser.