source: trunk/doc/historical/development/plugins/index.html @ 7982

Last change on this file since 7982 was 7982, checked in by Nicklas Nordborg, 22 months ago

Merge BASE 3.18.2 to the trunk

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