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 ©</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"> |
---|
116 | private 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 | |
---|
129 | public 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"> |
---|
164 | public 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"> |
---|
239 | protected SessionControl sc = null; |
---|
240 | protected ParameterValues configuration = null; |
---|
241 | protected 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 | */ |
---|
248 | public 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"> |
---|
294 | public 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 | */ |
---|
339 | public 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<GuiContext> 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 |
---|
406 | private static final Set<GuiContext> guiContexts = |
---|
407 | Collections.singleton(new GuiContext(Item.RAWBIOASSAY, GuiContext.Type.ITEM)); |
---|
408 | |
---|
409 | public Set<GuiContext> 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 | */ |
---|
443 | public 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 |
---|
508 | private RequestInformation configureJob; |
---|
509 | |
---|
510 | // The parameter that asks for a file to import from |
---|
511 | private PluginParameter<File> fileParameter; |
---|
512 | |
---|
513 | // The parameter that asks if existing items should be updated or not |
---|
514 | private PluginParameter<Boolean> updateExistingParameter; |
---|
515 | |
---|
516 | public 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 | */ |
---|
534 | private RequestInformation getConfigureJob() |
---|
535 | { |
---|
536 | if (configureJob == null) |
---|
537 | { |
---|
538 | fileParameter = new PluginParameter<File>( |
---|
539 | "file", |
---|
540 | "File", |
---|
541 | "The file to import the data from", |
---|
542 | new FileParameterType(null, true, 1) |
---|
543 | ); |
---|
544 | |
---|
545 | updateExistingParameter = new PluginParameter<Boolean>( |
---|
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<PluginParameter<?>> parameters = |
---|
555 | new ArrayList<PluginParameter<?>>(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"> |
---|
581 | StringParameterType stringPT = new StringParameterType(255, null, true); |
---|
582 | PluginParameter one = new PluginParameter("one", "One", "First string", stringPT); |
---|
583 | PluginParameter 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"> |
---|
626 | PluginParameter firstSection = new PluginParameter(null, "First section", null, null); |
---|
627 | PluginParameter secondSection = new PluginParameter(null, "First section", null, null); |
---|
628 | // ... |
---|
629 | |
---|
630 | parameters.add(firstSection); |
---|
631 | parameters.add(firstParameterInFirstSection); |
---|
632 | parameters.add(secondParameteInFirstSection); |
---|
633 | |
---|
634 | parameters.add(secondSection); |
---|
635 | parameters.add(firstParameterInSecondSection); |
---|
636 | parameters.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"> |
---|
674 | public 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<Throwable> 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><base-dir>/plugins/<name-of-plugin>/</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 --> Plugins --> Definitions</code> |
---|
771 | page. Click the <code>New…</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"> |
---|
835 | PLUGINNAME/ |
---|
836 | PLUGINNAME/bin/ |
---|
837 | PLUGINNAME/lib/ |
---|
838 | PLUGINNAME/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"> |
---|
864 | Manifest-Version: 1.0 |
---|
865 | Class-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><www-root>/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><www-root>/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:<name-of-parameter></code> |
---|
914 | for example: |
---|
915 | <pre class="code"> |
---|
916 | // Plugin generate PluginParameter |
---|
917 | StringParameterType stringPT = new StringParameterType(255, null, true); |
---|
918 | PluginParameter one = new PluginParameter("one", "One", "First string", stringPT); |
---|
919 | PluginParameter two = new PluginParameter("two", "Two", "Second string", stringPT); |
---|
920 | |
---|
921 | // JSP should name fiels as: |
---|
922 | First string: <input type="text" name="parameter:one"><br> |
---|
923 | Second stirng: <input type="text" name="parameter:two"> |
---|
924 | </pre> |
---|
925 | |
---|
926 | <li>Send the form to <code>index.jsp</code> with some parameters: |
---|
927 | |
---|
928 | <pre class="code"> |
---|
929 | <form action="index.jsp" method="post"> |
---|
930 | <input type="hidden" name="ID" value="<%=ID%>"> |
---|
931 | <input type="hidden" name="cmd" value="SetParameters"> |
---|
932 | ... |
---|
933 | </form> |
---|
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) |
---|
945 | final SessionControl sc = Base.getExistingSessionControl(pageContext, true); |
---|
946 | final String ID = sc.getId(); |
---|
947 | |
---|
948 | // Get information about the current request to the plugin |
---|
949 | PluginConfigurationRequest pcRequest = |
---|
950 | (PluginConfigurationRequest)sc.getSessionSetting("plugin.configure.request"); |
---|
951 | PluginDefinition plugin = |
---|
952 | (PluginDefinition)sc.getSessionSetting("plugin.configure.plugin"); |
---|
953 | PluginConfiguration pluginConfig = |
---|
954 | (PluginConfiguration)sc.getSessionSetting("plugin.configure.config"); |
---|
955 | PluginDefinition job = |
---|
956 | (PluginDefinition)sc.getSessionSetting("plugin.configure.job"); |
---|
957 | RequestInformation ri = pcRequest.getRequestInformation(); |
---|
958 | </pre> |
---|
959 | |
---|
960 | |
---|
961 | </body> |
---|
962 | </html> |
---|