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

Last change on this file since 3329 was 3329, checked in by Nicklas Nordborg, 16 years ago

References #548: Write "Installing plug-ins" section

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