27.8. Extension points defined by BASE

27.8.1. Menu: extensions
27.8.2. Toolbars
27.8.3. Edit dialogs
27.8.4. Bioassay set: Tools
27.8.5. Bioassay set: Overview plots
27.8.6. Services
27.8.7. Service actions
27.8.8. Connection managers
27.8.9. Fileset validators
27.8.10. Logging managers
The LogManagerFactory interface
The LogManager interface
The EntityLogger interface
27.8.11. Item overview loaders
27.8.12. Item overview validation
27.8.13. Item overview information
27.8.14. Table list columns
27.8.15. Query filters
27.8.16. Login manager
Supporting multiple installed login managers
Internal vs. external authentation
Configuration settings
27.8.17. Login form
The before-login event
27.8.18. Skins
27.8.19. Start page

In this section, we will give an overview of the extension points defined by BASE. Most extension points are used in the web client to add buttons and menu items, but there are a few for the core API as well.

27.8.1. Menu: extensions

Menu items can be added to the top-level Extensions menu. Actions should implement the interface: MenuItemAction

The MenuItemAction.getMenuType() provides support for MENUITEM, SUBMENU and SEPARATOR menus. Which of the other properties that are needed depend on the menu type. Read the javadoc for more information. The rendering is internal (eg. extensions can't provide their own renderers).

BASE ships with two action factories: FixedMenuItemFactory and PermissionMenuItemFactory. The fixed factory provides a menu that is the same for all users. The permission factory can disable or hide a menu depending on the logged in user's role-based permissions. The title, icon, etc. can have different values depending on if the menu item is disabled or enabled.

[Note] onclick has been removed

The onclick attribute has been deprecated since BASE 3.3 and was removed in BASE 3.5. Use a custom script instead to bind the click event with the menu item or <data-url> if the menu simply navigates to another page. <data-popup> can be used to open the page in a popup window and should contain three comma-separated values: name-of-window, width-of-window, height-of-window

27.8.2. Toolbars

Most toolbars on all list and single-item view pages can be extended with extra buttons. Actions should implement the interface: ButtonAction. Button actions are very simple and only need to provide things like a title, tooltip, icon, etc. This extension point has support for custom javascript, stylesheets and renderers. The default renderer is ToolbarButtonRendererFactory.

BASE ships with two action factories: FixedButtonFactory and PermissionButtonFactory. The fixed factory provides a toolbar button that is the same for all users. The permission factory can disable or hide a buton depending on the logged in user's role-based permissions. The title, icon, etc. can have different values depending on if the menu item is disabled or enabled.

[Note] onclick has been removed

The onclick attribute has been deprecated since BASE 3.3 and was removed in BASE 3.5. Use a custom script instead to bind the click event with the button. Download the example code to see it in action.

27.8.3. Edit dialogs

Most item edit dialogs can be extended with additional tabs. Actions should implement the interface: TabAction. The actions are, in principle, simple and only need to provide a title and content (HTML). The action may also provide javascripts for validation, etc. This extension point has support for custom stylesheets and javascript. Rendering is fixed and can't be overridden.

BASE ships with two action factories: FixedTabFactory and IncludeContentTabFactory. The fixed factory provides a tab with fixed content that is the same for all users and all items. This factory is not very useful in a real scenario. The other factory provides content by including the output from another resource, eg. a JSP page, a servlet, etc. The current context is stored in a request-scoped attribute under the key given by JspContext.ATTRIBUTE_KEY. A JSP or servlet should use this to hook into the current flow. Here is a code example:


// Get the JspContext that was created on the main edit page
final JspContext jspContext = (JspContext)request.getAttribute(JspContext.ATTRIBUTE_KEY);

// The current item is found in the context. NOTE! Can be null if a new item
final BasicItem item = (BasicItem)jspContext.getCurrentItem();

// Get the DbControl and SessionControl used to handle the request (do not close!)
final DbControl dc = jspContext.getDbControl();
final SessionControl sc = dc.getSessionControl();

The extra tab need to be paired with an extension that is invoked when the edit form is saved. Each edit-dialog extension point has a corresponding on-save extension point. Actions should implement the interface: OnSaveAction. This interface define three callback methods that BASE will call when saving an item. The OnSaveAction.onSave() method is called first, but not until all regular properties have been updated. If the transaction is committed the OnSaveAction.onCommit() method is also called, otherwise the OnSaveAction.onRollback() is called. The onSave() method can throw an exception that will be displayed to the user. The other callback method should not throw exceptions at all, since that may result in undefined behaviour and can be confusing for the user.

27.8.4. Bioassay set: Tools

The bioassay set listing for an experiment has a Tools column which can be extended by extensions. This extension point is similar to the toolbar extension points and actions should implement the interface: ButtonAction.

Note that the list can contain BioAssaySet, Transformation and ExtraValue items. The factory implementation need to be aware of this if it uses the JspContext.getItem() method to examine the current item.

27.8.5. Bioassay set: Overview plots

The bioassay set page has a tab Overview plots. The contents of this tab is supposed to be some kind of images that have been generated from the data in the current bioassay set. What kind of plots that can be generated typically depends on the kind of data you have. BASE ships with an extension (MAPlotFactory) that creates MA plots and Correction factor plots for 2-channel bioassays. Actions should implement the interface: OverviewPlotAction. A single action generates a sub-tab in the Overview plots tab. The sub-tab may contain one or more images. Each image is defined by a PlotGenerator which sets the size of the image and provides an URL to a servlet that generates the actual image. It is recommended that the servlet cache images since the data in a bioassay set never changes. The BASE core API provides a system-managed file cache that is suitable for this. Call Application.getStaticCache() to get a StaticCache instance. See the source code for the core PlotServlet for details of how to use the cache.

27.8.6. Services

A service is a piece of code that is loaded when the BASE web server starts up. The service is then running as long as the BASE web server is running. It is possible to manually stop and start services. This extension point is different from most others in that it doesn't affects the visible interface. Since services are loaded at startup time, this also means that the context passed to ActionFactory methods will have a special ClientContext associated with it. There is no current item, and no user is logged in. However, the SessionControl returned from ClientContext.getSessionControl() gives permission to imporsonate another making it possible for the extension to access the BASE database almost without limitation. There is no meaning for extensions to specify a RendererFactory. Service actions should implement the interface: ServiceControllerAction. The interface provides start() and stop() methods for controlling the service. BASE doesn't ship with any service, but there is an FTP service available at the BASE plug-ins site: https://baseplugins.thep.lu.se/wiki/net.sf.basedb.ftp

27.8.7. Service actions

The services list page has an Action column for showing the Start and Stop actions as well as any other actions extending this extension point. This extension point is similar to the toolbar extension points and actions should implement the ButtonAction interface. This extension point supports custom scripts and stylesheets. Note that all extensions are invoked for all services. The ClientContext.getItem() returns the ServiceControllerAction instance for the current service. Extensions should use this to decide if an action should be created or not.

27.8.8. Connection managers

This extension point adds support for using external files in BASE. This is a core extension point and is available independently of the web client. Actions should implement the interface: ConnectionManagerFactory.

The getDisplayName() and getDescription() methods are used in the gui when a user manually selects which connection manager to use. The supports(URI) is used when auto-selecting a connection manager based on the URI of the resource.

BASE ships with factory that supports HTTP and HTTPS file references: HttpConnectionManagerFactory. The BASE plug-ins site has an connection manager that support external files via FTP and SFTP: https://baseplugins.thep.lu.se/wiki/net.sf.basedb.xfiles

27.8.9. Fileset validators

In those cases where files are used to store data instead of importing it to the database, BASE can use extensions to check that the supplied files are valid and also to extract metadata from the files. For example, the CelValidationAction is used to check if a file is a valid Affymetrix CEL file and to extract data headers and the number of spots from it.

Validation and metadata extraction actions should implement the ValidationAction interface. This is a core extension point and is available independently of the web client.

This extension point is a bit more complex than most other extension points. To begin with, the factory class will be called with the owner of the file set as the current item. Eg. the ClientContext.getCurrentItem() should return a FileStoreEnabled item. It is recommended that the factory performs a pre-filtering of the items to avoid calling the actual validation code on unsupported files. For example, the CelValidationFactory will check that the item is a RawBioAssay item using the Affymetrix platform.

Each file in the file set is then passed to the ValidationAction.acceptFile(FileSetMember) which may accept or reject the file. If the file is accepted it may be accepted for immediate validation or later validation. The latter option is useful in more complex scenarios were files need to be validated as a group. If the file is accepted the ValidationAction.validateAndExtractMetadata() is called, which is were the real work should happen.

The extensions for this extension point is also called when a file is replaced or removed from the file set. The calling sequence to set up the validation is more or less the same as described above, but the last step is to call ValidationAction.resetMetadata() instead of ValidationAction.validateAndExtractMetadata().

[Tip] Use the SingleFileValidationAction class.

Most validators that work on a single file at a time may find the SingleFileValidationAction class useful. Is should simplify the task of making sure that only the desired file type is validated. See the source code of the CelValidationAction class for an example.

27.8.10. Logging managers

This extension point makes it possible to detect changes that are made to item and generate log entries in real time. The core will send notifications for all item creations, updates and deletions to all registered logging managers. It is up to each implementation to decide if an event should be logged, where to log it and how much information that should be stored. The BASE core provides a logging implementation that save some information to the database which can also be viewed through the web interface.

The logging mechanism works on the data layer level and hooks into callbacks provided by Hibernate. EntityLogger:s are used to extract relevant information from Hibernate and create log entries. While it is possible to have a generic logger it is usually better to have different implementations depending on the type of entity that was changed. For example, a change in a child item should, for usability reasons, be logged as a change in the parent item. Entity loggers are created by a LogManagerFactory. All changes made in a single transaction are usually collected by a LogManager which is also created by the factory.

The LogManagerFactory interface

Each registered action factory shoulds create a LogManagerFactory that is used throughout a single transaction. If the factory is thread-safe, the same instance can be re-used for multiple requests at the same time. Here is a list of the methods the factory must implement:

public LogManager getLogManager(LogControl logControl);

Creates a log manager for a single transaction. Since a transaction is not thread-safe the log manager implementation doesn't have to be either. The factory has the possibility to create new log managers for each transaction.

public boolean isLoggable(Object entity);

Checks if changes to the given entity should be logged or not. For performance reasons, it usually makes sense to not log everything. For example, the database logger implementation only logs changes if the entity implements the LoggableData interface. The return value of this method should be consistent with getEntityLogger().

public EntityLogger getEntityLogger(LogManager logManager,
                                    Object entity);

Create or get an entity logger that knows how to log changes to the given entity. If the entity should not be logged, null can be returned. This method is called for each modified item in the transaction.

The LogManager interface

A new log manager is created for each transaction. The log manager is responsible for collecting all changes made in the transaction and store those changes in the appropriate place. The interface doesn't define any methods for this collection, since each implementation may have very different needs.

public LogControl getLogControl();

Get the log control object that was supplied by the BASE core when the transaction was started. The log controller contains methods for accessing information about the transaction, such as the logged in user, executing plug-in, etc. It can also be used to execute queries against the database to get even more information.

[Warning] Warning

Be careful about the queries that are executed by the log controller. Since all logging code is executed at flush time in callbacks from Hibernate we are not allowed to use the regular session. Instead, all queries are sent through the stateless session. The stateless session has no caching functionality which means that Hibernate will use extra queries to load associations. Our recommendation is to avoid quires that return full entities, use scalar queries instead to just load the values that are needed.

public void afterCommit(); , public void afterRollback();
Called after a successful commit or after a rollback. Note that the connection to the database has been closed at this time and it is not possible to save any more information to it at this time.

The EntityLogger interface

An entity logger is responsible for extracting the changes made to an entity and converting it to something that is useful as a log entry. In most cases, this is not very complicated, but in some cases, a change in one entity should actually be logged as a change in a different entity. For example, changes to annotations are handled by the AnnotationLogger which which log it as a change on the parent item.

public void logChanges(LogManager logManager,
                       EntityDetails details);

This method is called whenever a change has been detected in an entity. The details variable contains information about the entity and, to a certain degree, what changes that has been made.

27.8.11. Item overview loaders

The item overview functionality allow extensions to load more items in the tree structure. The extension point is a core extension point that is available independently of the web client. Actions should implement the ChildNodeLoaderAction interface. We recommend that the actions also extend the BasicItemNodeLoader since this will make it easier to handle situations with missing items, permission problems and validation. Since each registered extension is checked for each item in the overview we recommend that the action factory makes a first filtering step before an action is created. This should be relatively simple since the current item in the ClientContext is the parent node. For example:


// From the ActionFactory interface
public boolean prepareContext(InvokationContext<? super ChildNodeLoaderAction> context) 
{
   Node parentNode = (Node)context.getClientContext().getCurrentItem();
   if (parentNode != null)
   {
      if (parentNode.getItem() instanceof Ownable)
      {
         return true;
      }
   }
   return false;
}

27.8.12. Item overview validation

The item overview functionality allow extensions to add validation code to any item in the tree. There are two co-existing extension points, one for the actual validation code and one for the validation rule defintions. Both extension points are core extension points that is available independently of the web client. Validation actions should implement the NodeValidatorAction interface, which is simple the same as NodeValidator We recommend that the actions extend the NameableNodeValidator or BasicNodeValidator since this will make it easier to handle situations with missing items and permission problems. Since each registered extension is checked for each item in the overview we recommend that the action factory makes a first filtering step before an action is created. This should be relatively simple since the current item in the ClientContext is type of that should be validated. For example:


// From the ActionFactory interface
public boolean prepareContext(InvokationContext<? super NodeValidatorAction> context) 
{
   return context.getClientContext().getCurrentItem() == Item.USER;
}

Rule definition actions should implement the ValidationRuleAction. This is simply a container with some information about the validation rule, such as a title, description and a default severity level. The Validator class that ships with BASE already implements this interface. The ReflectValidationRuleActionFactory can be used to return an existing public static final rule definition as an action:


<extension
   id="validationrule.invalid-url"
   extends="net.sf.basedb.util.overview.validationrule">
   <index>4</index>
   <about>
      <name>Invalid URL</name>
      <description>Checks if an URL has a valid syntax.</description>
   </about>
   <action-factory>
      <factory-class>
         net.sf.basedb.util.overview.extensions.ReflectValidationRuleActionFactory
      </factory-class>
      <parameters>
         <field>net.sf.basedb.examples.extensions.overview.OwnerValidator.INVALID_URL</field>
      </parameters>
   </action-factory>
</extension>

27.8.13. Item overview information

The item overview functionality allow extensions to display additional information about the currently selected node in the right pane in the web interface. Each SectionAction object that is created by extensions will result in an additional section in the GUI. Note that all extension factories are called for all nodes. The ActionFactory.prepareContext() should be used to enable/disable the extension depending on the node that is selected. BASE ships with the IncludeContentSectionFactory factory implementation which allows a resourse (eg. another JSP script) to generate the content of the section. The current context is stored in a request-scoped attribute under the key given by JspContext.ATTRIBUTE_KEY. A JSP or servlet should use this to hook into the current flow. Here is a code example:


// Get the JspContext that was created on the main edit page
final JspContext jspContext = (JspContext)request.getAttribute(JspContext.ATTRIBUTE_KEY);

// The currently selected node is found in the context.
final Node node = (Node)jspContext.getCurrentItem();

// Get the DbControl and SessionControl used to handle the request (do not close!)
final DbControl dc = jspContext.getDbControl();
final SessionControl sc = dc.getSessionControl();

27.8.14. Table list columns

This extension point makes it possible to add more columns to most major list pages in the web interface. Actions should implement the ListColumnAction interface. This interface contains several methods which can be grouped into three main types:

  • Metadata methods, for example, the id and title of the column and if the column can be sorted, filtered, etc.

  • Rendering information, for example, CSS class and style information that is used for the column header.

  • Worker methods, that retrieve the value from the current item and format it for proper display. Different methods are used for the web display and for exporting to a file.

The AbstractListColumnBean class provides a bean-like implementation for all methods except the getValue() method. Extending from this class makes it easy to your own implementations. BASE ships with the PropertyPathActionFactory factory that can be used without coding if the added column can be expressed as a property path that can be handled by the Metadata class. As usual, see the example code for some examples.

27.8.15. Query filters

This extension point makes it possible to extend filtering functionality for queries that are generated by list pages via the ItemContext.configureQuery() method in the ItemContext class. Actions should implement the QueryFilterAction interface. This interface allows an extension to filter a query based on a single column filter, a filter row or a combination of everything.

Note that the ActionFactory.prepareContext() is called for all filter extensions at all times. It is recommended that the implementation checks the current context for the type of items in the list and if there is a filter in a column that the extension should handle. An extension may override the default implementation of any filter, but this is not recommended. The recommended design is to combine the filter extension with a custom column that uses the !x. prefix as the column id. Here is an example from the Variant Search extension (https://baseplugins.thep.lu.se/wiki/net.sf.basedb.varsearch):


public boolean prepareContext(InvokationContext<? super QueryFilterAction> context) 
{
   // Enabled in the RAWBIOASSAY list page
   // and if the !x.lucene column is present with a filter
   // and if the service database is running
   ItemContext ctx = context.getClientContext().getCurrentItem();
   return ctx.getItemType() == Item.RAWBIOASSAY && 
      ctx.hasExtensionFilter("lucene") && 
      VarSearchService.getInstance().isRunning();
}

27.8.16. Login manager

This extension point makes it possible to authenticate users by some other means than the regular internal username+password authentication. Authentication managers should implement the AuthenticationManager action interface. This interface is simple and the only method that is required to implement is the parameter-less authenticate() method. There are three outcomes:

  • A AuthenticatedUser is returned with information about a user that has passed authentication.

  • A null value is returned to indicate that the manager could not determine if the login credentials are valid or not. The BASE core may try another authentication manager or use internal authentication.

  • An exception is thrown to indicate that the manager has determined that the login credentials are invalid.

Since the action interface doesn't contain any parameters that contain information about the login request, the implementation need to get this from the ClientContext that is passed to the action factory. The getCurrentItem() item is a LoginRequest containing the login and password the user entered on the login page. The ClientContext ojbect can be cast to AuthenticationContext which provide some extra services to the authentication manager.

Supporting multiple installed login managers

As of BASE 3.14 it should be easier to support server configurations that include multiple login manages. The login-form attribute in the LoginRequest parameter contains the login form that was used by the user. A login manager should check this value to decide if the login request should be handled or not. Note that the value may be missing.

The AuthenticationManager interface also includes an optional method, vetoAuthenticatedUser(UserData, AuthenticatedUser). This method is only called if one of the installed login manager authenticated the user successfully. Then, all other installed login managers get a chance to raise a veto by throwing an exception from this method. A responsible login manager probably need to implement this method to detect if a user that is supposed to login with a specific method is trying to login with some other method. Examples of actual implementations can be found in the the YubiKey and OTP extensions.

Internal vs. external authentation

All login requests are always sent to registered authentication managers first. Internal authentication is only used if no authentication manager could validate the user. Even with external authentication it is possible to let BASE cache the logins/passwords. This makes it possible to login to BASE if the external authentication server is down.

[Note] Note

An external authentication server can only be used to grant or deny a user access to BASE. It cannot be used to give a user permissions, or put a user into groups or different roles inside BASE.

The authentication process goes something like this:

  • An external authentication manager determines that the login request is valid and the user is already known to BASE. If the extra information (name, email, phone, etc.) is supplied and the auth.synchronize setting is TRUE the extra information is copied to the BASE server.

  • An external authentication manager determines that the login request is valid, but the user is not known to BASE. This happens the first time a user logs in. BASE will create a new user account. If the authentication manager provides extra information, it is copied to the BASE server (even if auth.synchronize is not set). The new user account will get the default quota and be added to the all roles and groups which has been marked as default.

  • If password caching is enabled, the password is copied to BASE. If an expiration timeout has been set, an expiration date will be calculated and set on the user account. The expiration date is only checked when the external authentication server is down.

  • The external authentication manager says that the login is invalid or the password is incorrect. The user will not be logged in.

  • The authentication manager says that something else is wrong. If password caching is enabled, internal authentication will be used. Otherwise the user will not be logged in.

Configuration settings

The configuration settings for the authentication system are located in the base.config file. Here is an overview of the settings. For more information read the section called “Authentication section”.

auth.synchronize

If extra user information is synchronized at login time or not. This setting is ignored if the driver does not support extra information.

auth.cachepasswords

If passwords should be cached by BASE or not. If the passwords are cached a user may login to BASE even if the external authentication server is down.

auth.daystocache

How many days to cache the passwords if caching has been enabled. A value of 0 caches the passwords for ever.

27.8.17. Login form

This extension point is typically used in combination with a login manager to provide a customized login form. Extensions should implement the LoginFormAction action which is used to specify prompts, tooltips, help texts and styling information for the login and password fields. The extension point supports custom scripts and stylesheets.

[Note] As of BASE 3.14 it is possible to install multiple login managers

All installed and enabled login forms are available in a selection list from which the user can select to switch to another login form. Since BASE 3.19.3 custom scripts and stylesheets are only loaded for the currently active form. In earlier BASE versions, custom scripts and stylesheets were loaded for all installed login forms.

It is possible for both style sheets and scripts to verify that they are only used on the intended login form. BASE is setting two data-attributes on the <body> tag:

data-login-form

The ID of the currently active login form.

data-requested-form

The ID of the login form that was requested. This may differ from the currently active if it is not installed or enabled.

In CSS files, rules that are targeting elements on the login form should match against the data-login-form attribute in the selector, for example:

/* Example from the YubiKey login form */
body[data-login-form="net.sf.basedb.yubikey.login-form"] 
   #loginform #login-row th
{
   background-image: url('../images/yubico.png');
   background-position: right center;
   background-repeat: no-repeat;
   padding-right: 20px;
}

Scripts should also check the value of this attribute before doing stuff:

/* Examle from the OTP login form */
if (Data.get(document.body, 'login-form') != 'net.sf.basedb.otp.login-form') 
{
   // Not the OTP login form
   return;
}

When logging in, the ID of the selected login form is added to the LoginRequest with attribute name login-form so that login managers can pick it up and take action on it.

The before-login event

In BASE 3.19.3 the before-login was introduced. It is a custom event that is sent to the <form> tag just before the login form is submitted to the server. Extensions may add event listeners for this event if they need to take some action and they have the possibility to cancel the submission by calling the event.preventDefault() method.

This functionality is, for example, used by the WebAuthn extension, which need to contact the server to get a challenge, ask the user to insert and click on the security key, and then return the signed challenge in the extraField before the login form is actually submitted. Since all of this is handled asynchronously the before-login event need to be cancelled. The login form can be submitted by calling Login.submitLoginForm().

27.8.18. Skins

This extension point can be used to change the visual appearance of BASE. Contrary to other extension points, providing actions are optional. The skinning is done by having the action factory set style sheet and script files on the JspContext instance. Typically the style sheet override or set some properties defined by the BASE core stylesheets.

Actions may be provided and should then implement the SkinAction interface. Actions are needed if the skin extension wants to modify the fav-icon or include some data in a hidden <div> tag. For this to work, the action must also implement the DynamicActionAttributes interface, since only the dynamic attribute values are included in the web page.

BASE provides a simple action factory implementation, FixedSkinActionFactory, that supports all of the above.

27.8.19. Start page

This extension point can be used to change the start page that is loaded after a use logs in to BASE. The normal start page is the BASEHome page. Extensions should implement the StartPageAction interface. The current item in the JspContext is the currently logged in user. All possible start pages will be listed as a user configuration in the BASEPreferences dialog. The ID of the selected start page is stored as a user setting. When the user is logging in to BASE the extension is queried for the URL to load and the browser is redirected to that page.

BASE provides a simple action factory implementation: FixedStartPageFactory.