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.
Menu items can be added to the top-level
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.
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 |
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.
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. |
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.
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.
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.
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
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.
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
See also
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()
.
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.
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.
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 | |
---|---|
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();
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.
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; }
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>
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();
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.
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(); }
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.
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.
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 | |
---|---|
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.
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”.
If extra user information is synchronized at login time or not. This setting is ignored if the driver does not support extra information.
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.
How many days to cache the passwords if caching has been enabled. A value of 0 caches the passwords for ever.
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.
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()
.
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.
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 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
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
.