Wandora modules framework

From WandoraWiki
Revision as of 13:42, 21 March 2014 by Olli (Talk | contribs)

Jump to: navigation, search

Wandora contains a modular framework for setting up server applications. This can be used as part of a standard web app, or as a basis for a custom server application. It can also be used in the Embedded HTTP server. It could be used for other purposes than just server applications too but that is its primary purpose.

Contents

Introduction

The idea behind the framework is that the server application consists of several individual modules, each of which does one specific thing. The modules often have dependencies between them, but some may also perform a completely isolated task.

As an example, a typical application might consist of the following modules:

  • A topic map manager that provides a topic map for other modules and handles things related to the topic map.
  • An Apache Velocity engine module which handles interfacing with Velocity.
  • A template manager module which handles common tasks related to page templates.
  • Several template modules, each representing one page template. These will register themselves to the template manager so other modules can find them, and use the Velocity Engine module to render the page. The templates could also be other than Velocity templates, but currently Velocity is the only supported template engine.
  • A server module that receives HTTP requests and forwards them to other modules. Currently there are two options for this. One which interfaces a standard servlet container, such as Apache Tomcat, and one which interfaces with the Embedded HTTP server.
  • Several action modules, these are the logical entry points for outside users. They receive the requests from the server module and then process them. The action possibly does something on the server side and then gives back a result, typically by utilising one of the templates. Some of the action modules may utilise the topic map manager module to do something with the topic map it provides.

There are several other modules that an application could also utilise. Some examples are a database module which connects to a relational database, a module that sends email, modules that restrict access based on user authentication, and others.

Many of the modules work behind abstract interfaces with the idea that the implementation of a required module can easily be changed. The above list of modules already mentioned one such example: the two different options for the server module. Some others have been defined with an interface but currently there is only one implementation, like the templates. They could use any templating engine but currently Apache Velocity is the only supported one.

Example web app

Configuration file

Basic structure

All the modules are defined in a configuration file, typically called modulesconfig.xml. This is an XML file with a fairly simple structure. The root element is <options> and under it each module is listed in <module> elements. The <module> element contains the Java class name of the module, which must implement the Module interface. Parameters for the module can be provided as child elements of the <module> element in <param> elements. For example, the following includes two modules, with the latter one containing some initialisation parameters for the module.

<?xml version="1.0" encoding="UTF-8"?>
<options>
  <module class="org.wandora.modules.LoggingModule"></module>

  <module class="org.wandora.modules.topicmap.SimpleTopicMapManager">
    <param key="topicMap">topicmap.wpr</param>
    <param key="autoSave">true</param>
  </module>    
</options>

Module dependency solving

Some module may require other modules to be present. All the required modules must be included in the config file. Their order does not matter, the modules are automatically loaded in such an order that modules that require others are loaded after the modules they need. This also means that there cannot be cyclic dependencies between the modules, although there are ways to work around this.

Dependencies are solved automatically based on the required module class or interface. The config file just needs to contain a module of that class or one implementing, or extending, it. But in some cases there may be more than one valid option for the dependency and you need to specify which to use. There are two ways to solve this.

First, you may name modules and then explicitly specify which module to use. Specify the name of the module in a name attribute in the <module> element. Then in the module that needs to use the other module, add a child element <useService> which specifies the service. Like this:

<module class="org.wandora.modules.topicmap.SimpleTopicMapManager" name="tmmanager">
  <param key="topicMap">topicmap.wpr</param>
  <param key="autoSave">true</param>
</module>    
<module class="org.wandora.modules.topicmap.ViewTopicAction">
  <useService service="org.wandora.modules.topicmap.TopicMapManager" value="tmmanager"></useService>
  <param key="action">topic</param>
  <param key="templateKey">viewtopic</param>
</module>

In the service attribute you must specify which service this relates to and then in the value attribute you specify the name of the module to use.

The second option to resolve the conflict is to specify a priority for the modules. You do this with a priority attribute in the <module> element. Each module by default has priority 0. A module with a higher priority will be chosen over a module with a lower priority in the automatic dependency solving. A module with a negative priority will never be automatically used, it can only be used by explicitly specifying so with the <useService> element as shown above. The <useService> will always override any priorities.

In the following example, the second topic map manager would not automatically be chosen for other modules. If any module wishes to use that, it must be explicitly specified with the <useService> element as shown above.

<module class="org.wandora.modules.topicmap.SimpleTopicMapManager">
  <param key="topicMap">topicmap1.wpr</param>
  <param key="autoSave">true</param>
</module>    
<module class="org.wandora.modules.topicmap.SimpleTopicMapManager" priority="-1">
  <param key="topicMap">topicmap2.wpr</param>
</module>

Also, the modules themselves may employ other means to specify what to use. As an example, all Template modules register themselves to a TemplateManager. Modules which need templates then don't depend directly on the Template module but instead on the TemplateManager which has its own mechanisms for getting the correct Template module.

List of modules

Below is a list of high level modules that are ready to use as is with short descriptions of what they do. The lower level abstract modules are described in the module development section and the user control modules in the user control section.

org.wandora.modules package

  • DefaultReplacementsModule
The replacements system is a much lighter option to a full template engine. It's a simple search and replace based system that can be used by other modules to make any of their string values dynamic. The AbstractAction automatically uses any replacement module it can find, with the DefaultReplacementsModule being the only current implementation.
  • EmailModule
This module provides email services for others. You the SMTP server and any relevant details in the parameters.
  • GenericDatabaseInterface
This module provides relational database services. It uses JDBC and as such supports a wide range of different database servers. The server details are specified in the parameters.
  • LoggingModule
This module provides logging services for other modules. You should always have a logging module included. Depending on how you use the modules framework, one might be automatically included outside the config file.
  • NetAuthenticatorModule
If your server tries to request a resource through http, and the request must be authenticated with a user and a password, you can use this to add an authenticator for it. Note that this is not user authentication for incoming requests, this is for providing login details for requests the server initiates to other servers.

org.wandora.modules.cache package

  • DiskCacheModule

org.wandora.modules.servlet package

  • ChainedAction
  • GenericTemplateAction
  • ImageResizeAction
  • RedirectAction
  • RequestForwarder
  • SendEmailAction
  • TemplateManager
  • VelocityEngineModule
  • VelocityTemplate

org.wandora.modules.topicmap package

  • SimpleTopicMapManager
  • ViewTopicAction
  • WandoraTopicMapManager

User control

Module development

Module hierarchy

You can make your own modules by implementing the Module interface or extending one of the abstract classes partially implementing it. Obviously you will need extend a higher level class or implement a certain interface if you wish to make your class do a specific task in the framework. All actions need to derive from AbstractAction but there are also higher level options like GenericTemplateAction. In fact GenericTemplateAction can even work without any extension if you place the application specific code in the template itself. Below is a list of the most common extension points. More details of specific methods can be found in the javadoc.

  • Module
The root interface of the module class hierarchy. This is the most generic option available. But in most cases you should instead extend the AbstractAction instead which has basic implementations of all the methods.
  • AbstractModule
This is the most generic of the abstract classes implementing Module. All methods of the Module interface are implemented, but they don't really do anything useful without overriding some methods or adding functionality in other ways.
  • ScriptModule
This is a direct extension of AbstractModule which provides some scripting features in the modulesconfig.xml file for the module. Modules deriving from this can have scripts specified in the config which are ran when the module is initialised, stopped or started. You may want derive your class from this instead of AbstractModule to get these features, even if you don't immediately plan to use them. They can later be used to add triggers when a module is started or stopped.
  • ServletModule.RequestListener
This is the interface for action classes that respond to http requests.
  • AbstractAction
This is an abstract class that implements the ServletModule.RequestListener interface and can thus respond to http requests. It also extends ScriptModule, and thus AbstractModule, so it has basic module implementation as well. This is basically the most generic abstract class to extend for action type modules.
  • CachedAction
This is a direct extension of AbstractAction and provides caching options for the action. If your action can benefit from having action results cached, you should consider extending this action. Caching can always be also turned off in the config even if you do extend from this class.
  • GenericTemplateAction
This is a direct extension of CachedAction and adds template handling. This class doesn't have any abstract methods and is useful on its own without any extensions. It will take an http request, use the template specified in the config with the context also specified in the config and then return the result of running the template through the template engine. If you only require minimal application logic which can be placed in the template, then you can just use this class directly instead of extending it. Otherwise extend the class and add some application logic and then add more things in the context if needed.

Module life-cycle

The instantiation, initialisation, starting, stopping and dependency resolution is all handled by the ModuleManager. The first thing in the life-cycle of a module is it being instantiated with the parameterless constructor, which modules should have. It is possible to not have this constructor in a module, but then it cannot be instantiated by the module manager automatically, meaning that you cannot use it in the config file at all. You can still add it in the module manager in your own custom code.

The constructor should not perform any big operations. It is assumed that instantiating the module is a fast operation and practically cannot fail, i.e. it should not throw any exceptions. There are other points in the life-cycle of the module where more complicated initialisation should be done.

Next the init method of the module will be called. The init method is given the parameters specified in the config file. What should happen in the init method is to process the parameters, store them in internal fields if needed, and validate that their values are sensible. Nothing time consuming should be done here, or anything with permanent side effects. The init method of every module will be called, even if the module is never going to be started, so at this point you do not know if the module will ever actually be expected to do anything. However, you may throw a ModuleException to indicate that the initialisation failed. For example, if some required parameters were not provided or the values of the parameters aren't sensible.

Next method in the life-cycle is getDependencies. In here you can specify what modules your module depends on. You do this by calling the requireModule method of the manager, which will immediately throw an exception if no suitable module is found. You can also use the optionalModule method to specify an optional dependency. This will not throw an exception if the needed module is missing, but if it exists, it makes sure that the needed module is started before your module. Note that the module dependencies may depend on initialisation parameters.

After the dependencies have been solved, modules can be started. The module manager will call the start method. This is the place where you can start executing the actual job of the module. The start method itself should not block for too long, so you should do any potentially time consuming tasks asynchronously.

Finally modules are stopped by calling the stop method. You should stop all threads spawned by your module, perform any cleaning up duties, dereference large data structures so that they may be garbage collected and return your module in a state where it can be started again with the start method, which may or may not happen.

Refer to the javadoc for more details on all the specific methods and their parameters.

Personal tools