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

Last change on this file since 3246 was 3246, checked in by Martin Svensson, 16 years ago

The output files of docbook will be sorted into subdirectories and figures now has a relative uri

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