29.6. The Extensions API

29.6.1. The core part
29.6.2. The web client part

29.6.1. The core part

The Extensions API is divided into two parts. A core part and a web client specific part. The core part can be found in the net.sf.basedb.util.extensions package and it's sub-packages, and consists of two sub-parts:

  • An ExtensionsManager that keeps track of the JAR files on the file system containing extensions code. The manager can detect new, updated and deleted files and is used to load metadata information about the extensions and register them in the Registry so that they can be used. The manager is also used to install plug-ins.

  • A set of interface definitions which forms the core of the Extensions API. The interfaces defines, for example, what an Extension is, what an ActionFactory should do and a few other things.

Let's start by looking at the extensions manager and related classes.

Figure 29.22. The extensions manager

The extensions manager

The BASE application is using a single manager and a single registry (handled by the Application) class. The manager has been configured to look for extensions and plug-ins in the directory specified by the plugins.dir setting in base.config. Theoretically, a single manager can handle multiple directories, but we do not use that feature. The BASE core also include some special files that are added with the addURI() method. They contain definitions for the core extensions and core plug-ins and are shipped as XML files that reside inside the BASE core JAR files.

The ExtensionsManager.scanForChanges() method is called to initiate a check for new, updated and deleted files. The manager uses the XmlLoader to find information about each JAR or XML file it find in the directory. After the scan, the ExtensionsManager.getFiles() method can be used to find more information about each individual file, for example, if it is a new or modified file, if it contains valid extension definitions and information about the author, etc. This information is used by the installation wizard in the web client to display a dialog were the user can select which extensions to intall. See Figure 22.2, “Extensions and plug-ins installation wizard”. Note that no installation or other actions take place at this stage.

The ExtensionsManager.processFiles() method is called to actually do something. It needs an ExtensionsFileProcessor implementation as an argument. As you can see in the diagram above there are multiple implementations (all are not shown in the diagram), each with a very specific task. A processor is usually also paired with a filter to target it at files that fulfil some criteria, for example, only at valid extension files that has been updated. Typically, the ExtensionsManager.processFiles() method need to be called multiple times with different processor implementations to perform a full installation of an extension or plug-in. Here is a list of the various processors currently in use in BASE.

RegisterExtensionsProcessor

Is used to register extensions with the registry. Can be paired with different filters depending on when it is used. At BASE startup an InstalledFilter is used so that only installed extensions are registered.

UnregisterExtensionsProcessor

Is used to unregister extensions when a file has been deleted. This should always be paired with for example, a DeletedFilter.

UnregisterExtensionsProcessor

Is used to unregister extensions when a file has been deleted. This should always be paired with for example, a DeletedFilter.

ExtractResourcesProcessor

Is used to extract files from the JAR file to a local directory. This is currently used by the web client to extract JSP files, images, etc. that are needed by web client extensions.

DeleteResourcesProcessor

The opposite of ExtractResourcesProcessor.

PluginInstallationProcessor

Is used to install and register plug-ins.

DisablePluginsProcessor

Is used to disable plug-ins from extensions that have been removed.

MarkAsProcessedProcessor

This is usually the final processor that is called and reset the timestamp on all processed files so that the next time ExtensionsManger.scanForChanges() is called it will know what has been modified.

[Note] Note

This list contains the core processors only. The web client part is using some additional processors to perform, for example, servlet registration.

The result of the processing can be collected with a ProcessResults object and is then displayed to the user as in Figure 22.3, “Extensions and plug-ins installation results”. All of the above is only used when BASE is starting up and initializing the extensions system or when the server administrator is performing a manual installation or update. The next diagram shows the part of the extensions system that is used when actually using the extensions for what they are intended to do (the web client adds some extra features to this as well, but that is discussed later).

Figure 29.23. The main Extensions API

The main Extensions API

The Registry is one of the main classes in the extension system. All extension points and extensions must be registered before they can be used. Typically, you will first register extension points and then extensions, beacuse an extension can't be registered until the extension point it is extending has been registered.

An ExtensionPoint is an ID and a definition of an Action class. The other options (name, description, renderer factory, etc.) are optional. An Extension that extends a specific extension point must provide an ActionFactory instance that can create actions of the type the extension point requires.

Example 29.1. The menu extensions point

The net.sf.basedb.clients.web.menu.extensions extension point requires MenuItemAction objects. An extension for this extension point must provide a factory that can create MenuItemAction:s. BASE ships with default factory implementations, for example the FixedMenuItemFactory class, but an extension may provide it's own factory implementation if it wants to.


Call the Registry.useExtensions() method to use extensions from one or several extension points. This method will find all extensions for the given extension points. If a filter is given, it checks if any of the extensions or extension points has been disabled. It will then call ActionFactory.prepareContext() for all remaining extensions. This gives the action factory a chance to also disable the extension, for example, if the logged in user doesn't have a required permission. The action factory may also set attributes on the context. The attributes can be anything that the extension point may make use of. Check the documentation for the specific extension point for information about which attributes it supports. If there are any renderer factories, their RendererFactory.prepareContext() is also called. They have the same possibility of setting attributes on the context, but can't disable an extension.

After this, an ExtensionsInvoker object is created and returned to the extension point. Note that the ActionFactory.getActions() has not been called yet, so we don't know if the extensions are actually going to generate any actions. The ActionFactory.getActions() is not called until we have got ourselves an ActionIterator from the ExtensionsInvoker.iterate() method and starts to iterate. The call to ActionIterator.hasNext() will propagate down to ActionFactory.getActions() and the generated actions are then available with the ActionIterator.next() method.

The ExtensionsInvoker.renderDefault() and ExtensionsInvoker.render() are just convenience methods that will make it easer to render the actions. The first method will of course only work if the extension point is providing a renderer factory, that can create the default renderer.

[Note] Be aware of multi-threading issues

When you are creating extensions you must be aware that multiple threads may access the same objects at the same time. In particular, any action factory or renderer factory has to be thread-safe, since only one exists for each extension. Action and renderer objects should be thread-safe if the factories re-use the same objects.

Any errors that happen during usage of an extension is handled by an ErrorHandler. The core provides two implementations. We usually don't want the errors to show up in the gui so the LoggingErrorHandlerFactory is the default implementation that only writes to the log file. The RethrowErrorHandlerFactory error handler can be used to re-throw exceptions which usually means that they trickle up to the gui and are shown to the user. It is also possible for an extension point to provide its own implementation of an ErrorHandlerFactory.

29.6.2. The web client part

The web client specific parts of the Extensions API can be found in the net.sf.basedb.client.web.extensions package and it's subpackages. The top-level package contains the ExtensionsControl class which is used to administrate the extension system. It is more or less a wrapper around the ExtensionsManager provided by the core, but adds permission control and a few other things.

In the top-level package there are also some abstract classes that may be useful to extend for developers creating their own extensions. For example, we recommend that all action factories extend the AbstractJspActionFactory class. All web client extension points use the JspContext class instead of ClientContext. The JSP context provides some extra information about the current request.

The sub-packages to net.sf.basedb.client.web.extensions are mostly specific to a single extension point or to a specific type of extension point. The net.sf.basedb.client.web.extensions.menu package, for example, contains classes that are/can be used for extensions adding menu items to the Extensions menu. See Section 27.8, “Extension points defined by BASE” for more information about the extension points defined by BASE.

Figure 29.24. The web client part of the Extensions API

The web client part of the Extensions API

When the Tomcat web server is starting up, the ExtensionsServlet is automatically loaded. This servlet has as two purposes:

  • Initialise the extensions system by calling ExtensionsControl.init(). This will result in an initial scan for installed extensions. This means that the extension system is up an running as soon as the first user log's in to BASE. The processing scheme is slightly different from what is done when the core is setting up the initial manager. The major additions are:

    • The LoadServletsProcessor is used to load servlets that has been defined in META-INF/servlets.xml.

    • The ExtractResourcesProcessor is used to extract all files from resources/* in the JAR file to www/extensions/jar-name.jar/ in Tomcat's web application directory.

  • Act as a proxy for custom servlets defined by the extensions. URL:s ending with .servlet has been mapped to the ExtensionsServlet. When a request is made it will extract the name of the extension's JAR file from the URL, get the corresponding ServletWrapper and then invoke the custom servlet. More information can be found in Section 27.7, “Custom servlets”.

Using extensions only involves calling the ExtensionsControl.createContext() and ExtensionsControl.useExtensions() methods. This returns an ExtensionsInvoker object as described in the previous section.

To render the actions it is possible to either use the ExtensionsInvoker.iterate() method and generate HTML from the information in each action. Or (the better way) is to use a renderer together with the Render taglib.

To get information about the installed extensions, change settings, enabled/disable extensions, performing a manual installation, etc. use the ExtensionsControl.get() method. This will create a permission-controlled object. All users has read permission, administrators has write permission.

[Note] Note

The permission we check for is WRITE permission on the web client item. This means it is possible to give a user permissions to manage the extension system by assigning WRITE permission to the web client entry in the database. Do this from AdministrateClients.

The XJspCompiler is mapped to handle the compilation .xjsp files which are regular JSP files with a different extension. The difference is that the XJSP compiler include the extension's JAR file on the class path, which means that the JSP file can use classes that would otherwise be impossible. This feature is experimental and requires installing an extra JAR into Tomcat's lib directory. See Section 22.1.4, “Installing the X-JSP compiler” for more information.