Changeset 2151


Ignore:
Timestamp:
Apr 6, 2006, 3:00:15 PM (18 years ago)
Author:
Nicklas Nordborg
Message:

Added documentation 6a) How to write plug-ins

Location:
trunk
Files:
2 added
1 deleted
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/doc/development/index.html

    r2146 r2151  
    267267 
    268268  <dl>
    269   <dt>a) <a href="plugins/index.html">How to write plug-ins</a> - Draft</dt>
     269  <dt>a) <a href="plugins/index.html">How to write plug-ins</a></dt>
    270270  <dd>
    271271    Document describing how to write a plug-in for BASE 2.
    272272  </dd>
    273273
    274   <dl>
    275274  <dt>b) <a href="plugins/import/index.html">Plug-ins for importing data</a> - TODO</dt>
    276275  <dd>
  • trunk/doc/development/plugins/index.html

    r2043 r2151  
    33  $Id$
    44
    5   Copyright (C) 2006 Jari Häkkinen, Gregory Vincic
     5  Copyright (C) 2006 Jari Häkkinen, Gregory Vincic, Nicklas Nordborg
    66
    77  This file is part of BASE - BioArray Software Environment.
     
    4343  <div class="abstract">
    4444 
    45     Draft
     45    <p>
     46    These instructions currently cover how to create plug-ins with the
     47    Java programming language for use in BASE 2.
     48    </p>
    4649 
    4750    <p>
    4851    <b>Contents</b><br>
    49 
    50     These instructions currently cover how to create plug-ins with the
    51     Java programming language for use in BASE 2. The instructions will
    52     be expanded to other programming languages.
    53     </p>
    54 
     52    </p>
     53    <ol>
     54    <li><a href="#interfaces">Interfaces you need to implement</a>
     55      <ul>
     56      <li><a href="#plugin">The Plugin interface</a>
     57      <li><a href="#interactive">The InteractivePlugin interface</a>
     58      </ul>
     59    <li><a href="#packaging">Packaging and installing the plugin</a>
     60    <li><a href="#organize">How to organize your plugin project</a>
     61    </ol>
     62   
     63    <p>
     64    <b>See also</b>
     65    </p>
     66    <ul>
     67    <li><a href="../overview/core/plugins.html">Internals of the core API - Plugin execution</a>
     68    </ul>
     69   
    5570    <p class=authors>
    5671    <b>Last updated:</b> $Date$ <br>
     
    5974  </div>
    6075
    61 <p>
    62 A sample plug-in is available for
    63 download, <a href=JFreePlotPlugin.tar.gz>JFreePlotPlugin</a>
    64 </p>
    65 
    66 <h3>General steps to create a Java based plug-in</h3>
    67 
    68 <p>
    69 <ol type=i>
    70 
    71   <li>
    72     <p>
    73       Create the plug-in directory layout:
    74 <pre class="code">
    75   PLUGINNAME/
    76   PLUGINNAME/bin/
    77   PLUGINNAME/lib/
    78   PLUGINNAME/src/PATH/TO/PLUGINNAME
    79 </pre>
    80     </p>
    81   </li>
    82 
    83   <li>
    84     <p>
    85       Add a build file <tt>build.xml</t> in the <tt>PLUGINNAME</tt>
    86       root directory:
    87 <pre class="code">
    88   PLUGINNAME/
    89   PLUGINNAME/build.xml
    90 </pre>
    91     </p>
    92   </li>
    93 
    94   <li>
    95     <p>
    96       Configure <tt>build.xml</tt>, change the property value for
    97       <tt>plugin.name</tt> and <tt>src</tt>:
    98 <pre class="code">
    99   &lt;?xml version="1.0" encoding="UTF-8"?&gt;
    100   &lt;project name="${plugin.name}" default="build.plugin" basedir="."&gt;
    101     &lt;property name="plugin.name" value="PLUGINNAME" /&gt;
    102     &lt;property name="src" value="PATH/TO/PLUGINNAME"/&gt;
    103 
    104     &lt;path id="classpath"&gt;
    105       &lt;fileset dir="lib"&gt;
    106         &lt;include name="**/*.jar"/&gt;
    107       &lt;/fileset&gt;
    108     &lt;/path&gt;
    109     &lt;target name="build.plugin"  description="Compiles the plugin"&gt;
    110       &lt;javac
    111          encoding="ISO-8859-1"
    112          srcdir="${src}/${plugin.name}"
    113          destdir="bin/"
    114          classpathref="classpath"&gt;
    115       &lt;/javac&gt;
    116       &lt;jar
    117          jarfile="${plugin.name}.jar"
    118          basedir="bin"
    119          includes="**/*.class"
    120          /&gt;
    121     &lt;/target&gt;
    122   &lt;/project&gt;
    123 </pre>
    124     </p>
    125   </li>
    126 
    127   <li>
    128     <p> Implement the plug-in in the
    129     directory <tt>PLUGINNAME/src/path/to/PLUGINNAME/PLUGINNAME.java</tt>
    130     </p>
    131   </li>
    132 
    133   <li>
    134     <p> Build the plug0in with the <tt>ant</tt> tool, <i>i.e.</i>
    135     do <tt>cd PLUGINNAME ; ant</tt>. If all went
    136     fine <tt>PLUGINNAME/PLUGINNAME.jar</tt> will be created.
    137     </p>
    138   </li>
    139 
    140   <li>
    141     <p> Copy <tt>PLUGINNAME.jar</tt> and other external jar files that
    142    the plug-in depends on (jar files stored
    143    in <tt>PLUGINNAME/lib/</tt>)
    144    to <tt>/path/to/tomcat/webapps/base2/WEB-INF/lib/</tt>.
    145     </p>
    146   </li>
    147 
    148   <li>
    149     <p> Create a plug-in definition through the administration
    150     interface in BASE 2 web application.
    151     </p>
    152   </li>
    153 
    154   <li>
    155     <p>
    156       That's it.
    157     </p>
    158   </li>
    159 
    160 </ol>
    161 </p>
    162 
    163 <p>
    164 The problem now is to create the plug-in. Information about the
    165 plug-in interfaces are currently available in
    166 <a href=http://base.thep.lu.se/base2/api/develop/index.html>core
    167 javadoc</a> format.
    168 </p>
    169 
     76  <a name="interfaces"></a>
     77  <h2>1. Interfaces you need to implement</h2>
     78  <p>
     79  The Base2 core defined two interfaces that are vital for implementing plugins:
     80  </p>
     81 
     82  <ul>
     83  <li><code>net.sf.basedb.core.plugin.Plugin</code>
     84  <li><code>net.sf.basedb.core.plugin.InteractivePlugin</code>
     85  </ul>
     86 
     87  It is required that the <code>Plugin</code> interface is implemented, but the
     88  <code>InteractivePlugin</code> is optional, and is only needed if you want user
     89  interaction.
     90 
     91  <a name="plugin"></a>
     92  <h3>1.1 The <code>Plugin</code> interface</h3>
     93 
     94  <p>
     95  This interface defines five methods and must be implemented by all plugins:
     96  </p>
     97 
     98  <dl>
     99  <dt class="method">public About getAbout();</dt>
     100  <dd>
     101    <p>
     102    Return information about the plugin. Ie. The name, version and a short description
     103    about what the plugin does. The <code>About</code> object also has fields for
     104    naming the author and various other contact information. The returned information
     105    is copied by the core at installation time to the database. The only required information
     106    is the name of the plugin. All other fields may have null values.
     107    </p>
     108    <p>
     109    A typical implementation stores this information in a static field:
     110    </p>
     111   
     112    <pre class="code">
     113private static final About about =
     114   new AboutImpl
     115   (
     116      "Spot images creator",
     117      "Converts a full-size scanned image into smaller preview jpg " +
     118      "images for each individual spot.",
     119      "2.0",
     120      "2006, Department of Theoretical Physics, Lund University",
     121      null,
     122      "base@thep.lu.se",
     123      "http://base.thep.lu.se"
     124   );
     125   
     126public About getAbout()
     127{
     128   return about;
     129}
     130</pre>
     131  </dd>
     132 
     133  <dt class="method">public Plugin.MainType getMainType();</dt>
     134  <dd>
     135    <p>
     136    Return information about the main type of plugin. The <code>MainType</code>
     137    is an enumeration which defines five possible values:
     138    </p>
     139   
     140    <ul>
     141    <li><code>ANALYZE</code>: An analysis plugin
     142    <li><code>EXPORT</code>: A plugin the exports data
     143    <li><code>IMPORT</code>: A plugin that imports data
     144    <li><code>INTENSITY</code>: A plugin that calculates the original spot intensities
     145      from raw data
     146    <li><code>OTHER</code>: Any other type of plugin
     147    </ul>
     148
     149    <p>
     150    The returned value is stored in the database but is otherwise
     151    not used by the core. Client applications (such as the web client)
     152    will probably use this information to group the plugins. Ie. a button
     153    labeled <code>Export</code> will let you select among the export plugins.
     154    </p>
     155   
     156    <p>
     157    A typical implementation just return one of the values:
     158    </p>
     159   
     160    <pre class="code">
     161public Plugin.MainType getMainType()
     162{
     163   return Plugin.MainType.OTHER;
     164}
     165</pre>
     166
     167  </dd>
     168 
     169  <dt class="method">public void init(SessionControl sc,
     170    ParameterValues configuration, ParameterValues job)
     171        throws BaseException;</dt>
     172       
     173     <dd>
     174      <p>
     175      Prepare the plugin for execution (or configuration). If the plugin needs to
     176      do some initialization this is the place to do it. A typical implementation
     177      however only stores the passed parameters in instance variables for later use.
     178      </p>
     179     
     180      <p>
     181      The parameters passed to this method has vital information that is needed
     182      to execute the plugin. The <code>SessionControl</code> is a central core
     183      object which holds information about the logged in used and allows you
     184      to create <code>DbControl</code> objects which allows a plugin to connect
     185      to the database to read, add or update information. The two
     186      <code>ParameterValues</code> objects contains information about the parameters
     187      to the plugin. The <code>configuration</code> object holds all parameters
     188      stored together with a <code>PluginConfiguration</code> object in the database.
     189      The <code>job</code> object holds all parameters that are stored together with a
     190      <code>Job</code> object in the database.
     191      </p>
     192     
     193      <p>
     194      The difference between a plugin configuration and a job parameter is that
     195      a configuration is usually something an administrator sets up, while
     196      a job is an actual execution of a plugin. For example a configuration
     197      for an import plugin holds the regular expressions needed to parse a text
     198      file and find the headers, sections and data lines, while the job holds
     199      the file to parse.
     200      </p>
     201     
     202      <p>
     203      The <code>AbstractPlugin</code> contains an implementation of this method
     204    make the passed parameters available as protected
     205    instance variables. We recommend plugin developers to let their plugins
     206      extend this class since it also has some other useful methods. For example
     207      for validating parameters resulting from user interaction and to store these
     208      values in the database.
     209      </p>
     210     
     211      <p>
     212      The <code>AbstractPlugin</code> implementation of this method.
     213      <pre class="code">
     214protected SessionControl sc = null;
     215protected ParameterValues configuration = null;
     216protected ParameterValues job = null;
     217/**
     218   Store copies of the session control, plugin and job configuration. These
     219   are available to subclasses in the {@link #sc}, {@link #configuration}
     220   and {@link #job} variables. If a subclass overrides this method it is
     221   recommended that it also calls super.init(sc, configuration, job).
     222*/
     223public void init(SessionControl sc,
     224   ParameterValues configuration, ParameterValues job)
     225   throws BaseException
     226{
     227   this.sc = sc;
     228   this.configuration = configuration;
     229   this.job = job;
     230}
     231</pre>
     232     </dd>
     233 
     234  <dt class="method">public void run(Request request, Response response, ProgressReporter progress);</dt>
     235  <dd>
     236    <p>
     237    Runs the plugin. The <code>Request</code> parameter has no useful information
     238    and can be ignored. It was originally used for passing parameters to the plugin
     239    but this is now found in the two <code>ParameterValues</code> objects passed
     240    to the <code>init</code> method.
     241    </p>
     242   
     243    <p>
     244    The <code>ProgressReporter</code> can be used by a plugin to report it's progress
     245    back to the core. The core will usually send the progress information to the database,
     246    which allows users to see exactly how the plugin is progressing from the web
     247    interface. This parameter can be null, but if it isn't we recommend all plugins
     248    to use it. However, it should be used sparingly, since each call to set the progress
     249    results in a database update. If the execution involves several thousands of items
     250    it is a bad idea to update the progress after processing each one of them. A good starting
     251    point is to divide the work into 100 pieces each representing 1% of the work. Ie.
     252    if the plugin should export 100 000 items it should report progress after every 1000
     253    items.
     254    </p>
     255 
     256    <p>
     257    The <code>Response</code> parameter is used to tell the core if the plugin
     258    was successful or failed. Not setting a response is considered a failure by the
     259    core. From the <code>run</code> method it is only allowed to use the
     260    <code>setDone()</code> or the <code>setError()</code> methods.
     261    </p>
     262   
     263    <p>
     264    Here is a skeleton that we recommend each plugin to use in it's implementation
     265    of the <code>run</code> method:
     266    </p>
     267   
     268    <pre class="code">
     269public void run(Request request, Response response, ProgressReporter progress)
     270{
     271   // Open a connection to the database
     272   // sc is set by init() method
     273   DbControl dc = sc.newDbControl();
     274   try
     275   {
     276      // Insert code for plugin here
     277
     278      // Commit the work
     279      dc.commit();
     280      response.setDone("Plugin ended successfully");
     281   }
     282   catch (Throwable t)
     283   {
     284      // All exceptions must be catched and sent back
     285      // using the response object
     286      response.setError(t.getMessage(), Arrays.asList(t));
     287   }
     288   finally
     289   {
     290      // IMPORTANT!!! Make sure opened connections are closed
     291      if (dc != null) dc.close();
     292   }
     293}
     294</pre>
     295  </dd>
     296 
     297  <dt class="method">public void done();</dt>
     298  <dd>
     299    <p>
     300    Clean up all resources after executing the plugin. This method
     301    mustn't throw any exceptions.
     302    </p>
     303    <p>
     304    The <code>AbstractPlugin</code> contains an implementation of
     305    this method which simply sets the parameters passed to the <code>init</code>
     306    method to null:
     307    </p>
     308   
     309    <pre class="code">
     310/**
     311   Clears the variables set by the <code>init</code> method. If a subclass
     312   overrides this method it is recommended that it also calls <code>super.done()</code>.
     313*/
     314public void done()
     315{
     316   configuration = null;
     317   job = null;
     318   sc = null;
     319}
     320</pre>
     321 
     322 
     323  </dl>
     324 
     325  <a name="interactive"></a>
     326  <h3>1.2 The <code>InteractivePlugin</code> interface</h3>
     327
     328  <p>
     329  If you want the plugin to be able to interact with the user you must
     330  also implement this interface. This is probably the case for most plugins.
     331  Among the plugins supplied with the core of Base the <code>SpotImageCreator</code>
     332  is one plugin that doesn't interact with the user. Instead, the web client has
     333  special JSP pages that handles all the interaction, creates a job for it and
     334  sets the parameters. This, kind of hardcoded, approach can be used for other plugins
     335  as well, but then it usually requires modification of the client application as well.
     336  </p>
     337 
     338  <p>
     339  The <code>InteractivePlugin</code> has three main tasks: tell a client application
     340  where the plugin should be plugged in, ask users for parameters, and validate and store
     341  those parameters. It has four methods:
     342  </p>
     343 
     344  <dl>
     345  <dt class="method">public Set&lt;GuiContext&gt; getGuiContexts();</dt>
     346  <dd>
     347    <p>
     348    Return information about where the plugin should be plugged in. Each
     349    place is identified by a <code>GuiContext</code> object, which is
     350    an <code>Item</code> and a <code>Type</code>. The item is one of the
     351    objects defined by the <code>net.sf.basedb.core.Item</code> enumeration
     352    and the type is either <code>Type.LIST</code> or <code>Type.ITEM</code>.
     353    </p>
     354   
     355    <p>
     356    For example, the <code>GuiContext = (Item.REPORTER, Type.LIST)</code>
     357    tells a client application that this plugin can be plugged in whenever
     358    a list of reporters is displayed. The
     359    <code>GuiContext = (Item.REPORTER, Type.ITEM)</code> tells a client application
     360    that this plugin can be plugged in whenever a list of reporters is displayed.
     361    The first case may be appropriate for a plugin that imports or exports reporters.
     362    The second case may be used by a plugin that updates the reporter information
     363    from an external source (well, it may make sense to use this in the list case
     364    as well).
     365    </p>
     366   
     367    <p>
     368    The returned information is copied by the core at installation time
     369    to make it easy to ask for all plugins for a certain <code>GuiContext</code>.
     370    </p>
     371
     372    <p>
     373    A typical implementation creates a static <b>unmodifable</b> <code>Set</code>
     374    which is returned by this method. It is important that the returned set can't
     375    be modified, since it may be a security issue if a bad behaving client
     376    application does that.
     377    </p>
     378   
     379    <pre class="code">
     380// From the net.sf.basedb.plugins.RawDataFlatFileImporter plugin
     381private static final Set&lt;GuiContext&gt; guiContexts =
     382   Collections.singleton(new GuiContext(Item.RAWBIOASSAY, GuiContext.Type.ITEM));
     383
     384public Set&lt;GuiContext&gt; getGuiContexts()
     385{
     386   return guiContexts;
     387}
     388</pre>   
     389  </dd>
     390 
     391  <dt class="method">public boolean isInContext(GuiContext context, Object item);</dt>
     392  <dd>
     393    <p>
     394    This method is called to check if a particular item is usable for the plugin,
     395    when the context type is <code>Type.ITEM</code>. Ie. the user has selected
     396    a specific <code>SAMPLE</code> and the the client application is now displaying
     397    information about that sample. Thus, our <code>GuiContext = (Item.SAMPLE, Type.ITEM)</code>.
     398    Now, the client application asks for a list of plugins supporting this context
     399    and for each one in the list calls this method with the current sample as
     400    the <code>item</code> parameter. The plugin should answer if it can do whatever
     401    it is supposed to do by returning <code>true</code> or <code>false</code>.
     402    </p>
     403   
     404    <p>
     405    Here is a real example from the <code>RawDataFlatFileImporter</code> plugin
     406    which imports raw data to a <code>RawBioAssay</code>. Thus,
     407    <code>GuiContext = (Item.RAWBIOASSAY, Type.ITEM)</code>, but the plugin can
     408    only import data if there isn't any already, and if the raw bioassay has the
     409    same raw data type as the plugin has been configured for.
     410    </p>
     411   
     412    <pre class="code">
     413/**
     414   Returns TRUE if the item is a {@link RawBioAssay} of the correct
     415   {@link RawDataType} and doesn't already have spots.
     416*/
     417public boolean isInContext(GuiContext context, Object item)
     418{
     419   boolean inContext = false;
     420   if (item instanceof RawBioAssay)
     421   {
     422      RawBioAssay rba = (RawBioAssay)item;
     423      if (rba.getSpots() == 0)
     424      {
     425         String actualRawDataType = rba.getRawDataType().getId();
     426         String configuredRawDataType = (String)configuration.getValue("rawDataType");
     427         inContext = actualRawDataType.equals(configuredRawDataType);
     428      }
     429   }
     430   return inContext;
     431}
     432</pre>
     433 
     434  </dd>
     435 
     436  <dt class="method">public RequestInformation getRequestInformation(GuiContext context, String command)
     437    throws BaseException;</dt>
     438  <dd>
     439    <p>
     440    Ask the plugin for parameters that needs to be entered by the user. The
     441    <code>GuiContext</code> parameter is one of the contexts returned by the
     442    <code>getGuiContexts</code> method. The command is string telling the plugin
     443    what command was executed. There are two predefined commands but as you will see
     444    the plugin may define it's own commands. The two predefined commands are defined
     445    in the <code>net.sf.basedb.core.plugin.Request</code> class:
     446    </p>
     447   
     448    <ul>
     449    <li><code>Request.COMMAND_CONFIGURE_PLUGIN</code>: Used when an administator is
     450      initiating a configuration of the plugin.
     451    <li><code>Request.COMMAND_CONFIGURE_JOB</code>: Used when a user has selected
     452      the plugin for running a job.
     453    </ul>
     454   
     455    <p>
     456    Given this information the plugin must return a <code>RequestInformation</code>
     457    object. This is simply a title, a description and a list of parameters.
     458    Usually the title will end up as the input form title and the description
     459    as a help text for the entire form. Do not put information about the
     460    individual parameters in this description, since each parameter has a
     461    description of their own.
     462    </p>
     463   
     464    <p>
     465    For example, when runing an import plugin it needs to ask for the file to
     466    import from and if existing items should be updated or not:
     467    </p>
     468
     469    <pre class="code">
     470// The complete request information
     471private RequestInformation configureJob;
     472
     473// The parameter that asks for a file to import from
     474private PluginParameter&lt;File&gt; fileParameter;
     475
     476// The parameter that asks if existing items should be updated or not
     477private PluginParameter&lt;Boolean&gt; updateExistingParameter;
     478
     479public RequestInformation getRequestInformation(GuiContext context, String command)
     480   throws BaseException
     481{
     482   RequestInformation requestInformation = null;
     483   if (command.equals(Request.COMMAND_CONFIGURE_PLUGIN))
     484   {
     485      requestInformation = getConfigurePlugin();
     486   }
     487   else if (command.equals(Request.COMMAND_CONFIGURE_JOB))
     488   {
     489      requestInformation = getConfigureJob();
     490   }
     491   return requestInformation;
     492}
     493
     494/**
     495   Get (and build) the request information for starting a job.
     496*/
     497private RequestInformation getConfigureJob()
     498{
     499   if (configureJob == null)
     500   {
     501      fileParameter = new PluginParameter&lt;File&gt;(
     502         "file",
     503         "File",
     504         "The file to import the data from",
     505         new FileParameterType(null, true, 1)
     506      );
     507     
     508      updateExistingParameter = new PluginParameter&lt;Boolean&gt;(
     509         "updateExisting",
     510         "Update existing items",
     511         "If this option is selected, already existing items will be updated " +
     512         " with the information in the file. If this option isn't selected " +
     513         " existing items are left untouched.",
     514         new BooleanParameterType(false, true)
     515      );
     516
     517      List&lt;PluginParameter&lt;?&gt;&gt; parameters =
     518         new ArrayList&lt;PluginParameter&lt;?&gt;&gt;(2);
     519      parameters.add(fileParameter);
     520      parameters.add(updateExistingParameter);
     521     
     522      configureJob = new RequestInformation
     523      (
     524         Request.COMMAND_CONFIGURE_JOB,
     525         "Select a file to import items from",
     526         "TODO - description",
     527         parameters
     528      );
     529   }
     530   return configureJob;
     531}
     532</pre>
     533
     534    <p>
     535    As you can see it takes some code to put together a <code>RequestInformation</code>
     536    object. For each parameter needed you need one <code>PluginParameter</code>
     537    object and one <code>ParameterType</code> object. Actually, a <code>ParameterType</code>
     538    can be reused for more than one <code>PluginParameter</code>. For example, if
     539    your plugin need 10 string which all are required you can use a single
     540    <code>ParameterType</code> for all of them:
     541    </p>
     542   
     543    <pre class="code">
     544StringParameterType stringPT = new StringParameterType(255, null, true);
     545PluginParameter one = new PluginParameter("one", "One", "First string", stringPT);
     546PluginParameter two = new PluginParameter("two", "Two", "Second string", stringPT);
     547// ... and so on
     548</pre>
     549
     550  <p>
     551  The <code>ParameterType</code> is an abstract base class for several subclasses
     552  each implementing a specific type of parameter. The list of subclasses may
     553  grow in the future, but here are the most important ones currently implemented:
     554  </p>
     555
     556  <p>
     557  Note! Most parameter types include support for suppying a predefined list
     558  of options to select from. In that case the list will be displayed as drop-down
     559  list for the user, otherwise a free input field is used.
     560  </p>
     561
     562  <ul>
     563  <li><code>StringParameterType</code>: Asks for a string value. Includes
     564    an option for specifying the maximum length of the string.
     565  <li><code>FloatParameterType, DoubleParameterType, IntegerParameterType, LongParameterType</code>:
     566    Asks for numerical values. Includes options for specifying a range (min/max)
     567    of allowed values.
     568  <li><code>BooleanParameterType</code>: Asks for a boolean value.
     569  <li><code>DateParameterType</code>: Asks for a date.
     570  <li><code>FileParameterType</code>: Asks for a file item.
     571  <li><code>ItemParameterType</code>: Asks for any other item. This parameter
     572    type requires that a list of options is supplied, except when the item type
     573    asked for matches the current <code>GuiContext</code>, in which case the
     574    currently selected item is used as the parameter value.
     575  <li><code>PathParameterType</code>: Ask for a path to a file or directory.
     576    The path may be non-existing and should be used when a plugin needs an
     577    output destination. Ie. the file to export to, or a directory
     578    where the output files should be placed.
     579  </ul>
     580
     581  </dd>
     582 
     583  <dt class="method">public void configure(GuiContext context, Request request, Response response);</dt>
     584  <dd>
     585    <p>
     586    Sends parameter values entered by the user for processing by the plugin.
     587    Typically the plugin should validate that the parameter values are correct
     588    and then store them in database.
     589    </p>
     590   
     591    <p>
     592    No validation is done by the core, except converting the input to the
     593    correct object type, ie. if the parameter asked for a Float the input string
     594    is parsed and converted to a Float. If you have extended the
     595    <code>AbstractPlugin</code> class it is very easy to validate the parameters
     596    using it's <code>validateRequestParameters()</code> method. This method
     597    takes the same list of <code>PluginParameter</code>:s used in the
     598    <code>RequestInformation</code> object and uses that information for validation.
     599    It returns null or a list of <code>Throwable</code>.
     600    </p>
     601   
     602    <p>
     603    When the parameters have been validated thay need to be stored. Once again, it is
     604    very easy if you use one of the <code>AbstractPlugin.storeValue()</code> or
     605    <code>AbstractPlugin.storeValues()</code> methods.
     606    </p>
     607   
     608    <p>
     609    The <code>configure</code> method works much like the <code>Plugin.run</code>
     610    method. It must return the result in the <code>Response</code> object.
     611    Ie. it shouldn't trow any exceptions. Here is an example of part of an
     612    implementation (building on the example above).
     613    </p>
     614   
     615    <pre class="code">
     616public void configure(GuiContext context, Request request, Response response)
     617{
     618   String command = request.getCommand();
     619   try
     620   {
     621      if (command.equals(Request.COMMAND_CONFIGURE_PLUGIN))
     622      {
     623         // TODO
     624      }
     625      else if (command.equals(Request.COMMAND_CONFIGURE_JOB))
     626      {
     627         // Validate user input
     628         List&lt;Throwable&gt; errors =
     629            validateRequestParameters(getConfigureJob().getParameters(), request);
     630         if (errors != null)
     631         {
     632            response.setError(errors.size() +
     633               " invalid parameter(s) were found in the request", errors);
     634            return;
     635         }
     636         
     637         // Store user input
     638         storeValue(job, request, fileParameter);
     639         storeValue(job, request, updateExistingParameter);
     640         
     641         // We are happy and done
     642         response.setDone("Job configuration complete", Job.ExecutionTime.SHORT);
     643         // TODO - check file size to make a better estimate of execution time
     644      }
     645   }
     646   catch (Throwable ex)
     647   {
     648      response.setError(ex.getMessage(), Arrays.asList(ex));
     649   }
     650}
     651</pre>   
     652   
     653    <p>
     654    Note that the <code>setDone()</code> has a second parameter <code>Job.ExecutionTime</code>.
     655    It is an indication about how long time it will take to execute the plugin. This is
     656    of interest for job queue managers which probably doesn't want to start too many
     657    long-running jobs at the same time blocking the entire system. Please
     658    try to use this parameter wisely and not use the <code>SHORT</code> value out of
     659    old habit all the time.
     660    </p>
     661   
     662    <p>
     663    The response also has a <code>setContinue()</code> method which tells the core
     664    that the plugin needs more parameters. Ie. the core will then call
     665    <code>getRequestInformation()</code> again with the new command, let the user
     666    enter values, and the call <code>configure()</code> with the new values.
     667    This process is repeated until the plugin reports that it is done or
     668    an error occurs.
     669    </p>
     670
     671    <p>
     672    An important note is that during this iteration it is the same instance
     673    of the plugin that is used. However, no parameter values are stored in the database
     674    until <code>setDone()</code> is called. Then, the plugin instance is usually
     675    discarded. The execution of the plugin happens in a new instance and maybe
     676    on a different server.
     677    </p>
     678   
     679    <p>
     680    Tip! You doesn't have to store all values the plugin asked for in the first
     681    place. You may even choose to store different values than those that were
     682    entered. For example, you might ask for the mass and height of a person and
     683    then only store the body mass index, which is calculated from those values.
     684    </p>
     685
     686
     687  </dd>
     688 
     689  </dl>
     690 
     691  <a name="packaging"></a>
     692  <h2>2. Packaging and installing the plugin </h2>
     693 
     694  <p>
     695  We recommend that each plugin or group of related plugins are compiled
     696  separately. To be able to use the plugin it must be put in a JAR file.
     697  Place the JAR file on the server <b>outside</b> the web servers classpath, ie. not in
     698  the <code>WEB-INF/lib</code>. Our recommendation is to place the plugin JAR in
     699  <code>&lt;base-dir&gt;/plugins/&lt;name-of-plugin&gt;/</code>
     700  </p>
     701  <img src="install_plugin.png" alt="How to install a plugin" align="right">
     702
     703  <p>
     704  The main benefit from placing the JAR file outside the classpath is that
     705  Base uses it's own classloader that supports unloading of the classes as well.
     706  This means that you may replace the JAR file with a new version without
     707  restarting the web server.
     708  </p>
     709 
     710  <p>
     711  Then, to install the plugin log in a an administrator and go to the
     712  <code>Administrate --&gt; Plugins --&gt; Definitions</code>
     713  page. Click the <code>New&hellip;</code> button and enter the
     714  class name and the path to the JAR file in the form that opens
     715  in the popup window.
     716  </p>
     717 
     718  <p>
     719  When you click save, the Base class loader will load the specified JAR file
     720  and class and check that it implements the <code>Plugin</code> interface.
     721  Then, it creates an instance of that class, calls <code>Plugin.getAbout()</code>
     722  and <code>Plugin.getMainType()</code>. If it is an <code>InteractivePlugin</code>
     723  it will also call <code>InteractivePlugin.getGuiContexts()</code>. This information
     724  is stored in the database.
     725  </p>
     726 
     727  <p>
     728  The installation will do one more thing. It will check which other interfaces the
     729  plugin implements and check against the list of registered <code>PluginType</code>:s.
     730  The <code>PluginType</code> system has not really been put into use yet. The core
     731  defines the <code>AutoDetectingImporter</code> which can be used for all import plugins
     732  that supports automatic detection of file formats. Read more about this in the
     733  <a href="import/index.html">Plug-ins for importing data</a> document.
     734  </p>
     735 
     736  <p>
     737  Now the administrator may continue by creating a new configuration for the
     738  plugin (assuming that is an <code>InteractivePlugin</code>. When the
     739  administrator starts the configuration sequence the
     740  following will happen:
     741  </p>
     742 
     743  <ul>
     744  <li>The core creates a new instance of the plugin.
     745  <li>Call the <code>Plugin.init()</code> method.
     746  <li>Call the <code>InteractivePlugin.getRequestInformation()</code> method,
     747    with <code>command = Request.COMMAND_CONFIGURE_PLUGIN</code> and a null
     748    <code>GuiContext</code>.
     749  <li>Display the list of parameters and let the user enter values.
     750  <li>Call <code>InteractivePlugin.configure()</code>.
     751  <li>If the plugin wants more parameters the above two steps are repeated
     752    but with the command returned in the response. Note! Be careful
     753    so you don't create infinite loops.
     754  <li>If the plugin reports that it is done, <code>Plugin.done()</code>
     755    is called and the plugin instance is discarded.
     756  </ul>
     757 
     758  <p>
     759  The steps for creating a new job follows the same procedure except that
     760  the first command is <code>Request.COMMAND_CONFIGURE_JOB</code> and
     761  the <code>GuiContext</code> isn't null.
     762  </p>
     763 
     764 
     765  <a name="organize"></a>
     766  <h2>3. How to organize your plugin project</h2>
     767  <p>
     768  Here is a simple example of how you might organize your project using
     769  ant (<a href="http://ant.apache.org">http://ant.apache.org</a>) as the build tool.
     770  This is just a recommendation that we have found to be working well. You may choose
     771  to do it another way.
     772  </p>
     773 
     774  <h3>3.1 Directory layout</h3>
     775 
     776  <pre class="code">
     777PLUGINNAME/
     778PLUGINNAME/bin/
     779PLUGINNAME/lib/
     780PLUGINNAME/src/org/company/
     781</pre>
     782 
     783  <p>
     784  The <code>bin/</code> directory is empty to start with. It will contain the
     785  compiled code. The <code>lib/</code> directory contains the JAR files
     786  your plugin uses (including the BASE2Core.jar). The <code>src/</code>
     787  directory contains your source code.
     788  </p>
     789 
     790  <h3>3.2 Ant build file</h3>
     791 
     792  <p>
     793  In the root of your directory, create the build file: <code>build.xml</code>.
     794  Here is an example that will compile your plugin and put it in a JAR file.
     795  </p>
     796 
     797  <iframe src="build.txt" width="100%" height="550" class="code" ></iframe>
     798   
     799  <p>
     800  If your plugin depends on other JAR files than the <code>Base2Core.jar</code>
     801  you should list them in the MANIFEST.MF file. Otherwise you should remove the
     802  <code>manifest</code> attribute of the <code>jar</code> tag in the build file.
     803  </p>
     804 
     805  <pre class="code">
     806Manifest-Version: 1.0
     807Class-Path: OtherJar.jar ASecondJar.jar
     808</pre>
     809
     810  <h3>3.3 Building the plugin</h3>
     811  <p>
     812  Compile the plugin simply by typing <code>ant</code> in the console
     813  window. If all went well the <code>MyPlugin.jar</code> will be
     814  created in the same directory.
     815  <p>
     816 
     817  <p>
     818  To install the plugin copy the JAR file to the server including the
     819  dependent JAR files (if any). Place all files together in the same
     820  directory. Then follow the instructions in section 2
     821  for making Base aware of the plugin.
     822  </p>
     823 
    170824</body>
    171825</html>
  • trunk/src/core/net/sf/basedb/plugins/RawDataFlatFileImporter.java

    r1795 r2151  
    250250    {
    251251      RawBioAssay rba = (RawBioAssay)item;
    252       String rawDataType = (String)configuration.getValue(rawDataTypeParameter.getName());
    253       inContext = rba.getRawDataType().getId().equals(rawDataType) && rba.getSpots() == 0;
     252      if (rba.getSpots() == 0)
     253      {
     254        String rawDataType = (String)configuration.getValue(rawDataTypeParameter.getName());
     255        inContext = rba.getRawDataType().getId().equals(rawDataType);
     256      }
    254257    }
    255258    return inContext;
Note: See TracChangeset for help on using the changeset viewer.