Wolframe, 0.0.3

Wolframe, a modular, 3-tier application server written in C++

Writing modules for Wolframe

Introduction

In Wolframe we can write modules for extending our application. Modules are units that export objects that can be used as components. The main source file of the module contains the following code block that declares the objects to be exported by the module. For using the macros shown in the following examples, we have to include the file include/appDevel.hpp or the sub-include files needed.

WF_MODULE_BEGIN( "MyModule", "this text is a short, one line description of the module")
// ... here we declare the components to export

Module component types

  • Command handler: A command handler (_Wolframe::cmdbind::CommandHandler) implements a sub part of the client server protocol. They are declared in the section Processor of the main configuration. Currently there exist only two types of command handlers loadable from modules:
    • the standard command handler also called direct map. The standard command handler delegates the requests to functions to execute. It uses the filter modules to get an iterator on the input to pass to functions to execute. It uses forms declared to validate input and output.
    • the lua command handler
    See Command handler module. A real example can be found in src/modules/cmdbind/directmap/.
  • Document type detection: We need for each document format processed a document type detection (_Wolframe::cmdbind::DoctypeDetector) that extracts the document type information (_Wolframe::types::DoctypeInfo). This info structure is needed by command handlers to associate a document with a function to execute. Currently there are two document type detection modules implemented:
    • XML
    • JSON
    See Document type detection module.
  • Filter type: Filters (_Wolframe::langbind::Filter) are used to parse a document and to create a unified representation for processing it and contrarywise map the unified representation to a document. Hence a filter consist of two parts, an input filter (_Wolframe::langbind::InputFilter) and an output filter (_Wolframe::langbind::OutputFilter). The unified representation for filters is a structure with the document meta data as key/value pairs and a sequence of content elements of the following types:
    • OpenTag: Open a substructure. The value is the name of the structure opened or if empty, defining a new array element
    • CloseTag: Close a substructure or an array element or it is marking the end of content (final close)
    • Value: Defines an atomic element
    • Attribute: Defines an attribute name, the following element is the attribute value (as type value).
    Filters define flags (_Wolframe::langbind::FilterBase::Flags) to level out language differences between producer and consumer of the filter element sequence. The initialization of the flags define a contract between producer and consumer. The idea behind is that not the weakest peer involved defines globally what information is transmitted with an filter. The consumer and the producer set some flags of the filter that describe the requirements of the consumer and the capabilities of the producer. There are two flags set by the producer:
    • PropagateNoCase tells the consumer that the tag names used are case insensitive
    • PropagateNoAttr tells the consumer that producer does not know about attributes (only OpenTag,CloseTag,Value are used)
    There is one flag set by the consumer:
    • SerializeWithIndices tells the producer that the consumer has no structure description that helps to distinguish between an array with one element or a single element. Therefore the consumer has to produce a sequence that contains the information if an element belongs to an array. This is done by having one named tag for the array structure and one tag without name for every array element. Lets take an array with name "Colors" two atomic elements "Red" and "Blue" as example. Instead of producing a sequence like this, if SerializeWithIndices is not set
      OpenTag "Colors",Value "Red",CloseTag,OpenTag "Colors",Value "Blue",CloseTag
      the following sequence has to be produced
      OpenTag "Colors",OpenTag,Value "Red",CloseTag,OpenTag,Value "Blue",CloseTag,CloseTag
    Unfortunately we cannot define a format with array indices as unified format, because there are languages that do not have the capability to produce this information (like for example XML). So we take the weakest form as base and provide an upgrade if required and if the model behind allows it. The method _Wolframe::langbind::InputFilter::setFlags( Flags f) can return false if it cannot provide the required information. See Filter module. As a real example we suggest to have a look at src/modules/filter/cjson/.
  • Form Function: Form functions (_Wolframe::langbind::FormFunction) are functions with a structure as input and a structure as output. The input structure is represented by an iterator implementing the input filter interface (iterator on OpenTag,CloseTag,Attribute,Value elements) with typed values instead of strings (_Wolframe::langbind::TypedInputFilter). In Wolframe any function in any language used for processing is implemented as form function. With this object type it is also possible to implement form functions in C++ (_Wolframe::serialize::CppFormFunction). See C++ form function module. As a real example we suggest to have a look at src/modules/function/graphix/.
  • Program type: Program types define the loading of objects into the program library (_Wolframe::prgbind::ProgramLibrary). Each program type declares a file type to be of its own and loads every file of this type configured with 'program' in the 'Processor' section of the configuration. See Program type module. As a real example we suggest to have a look at src/modules/cmdbind/aamap/.
  • DDL compiler: DDL (data definition language) compilers are compilers for forms used to validate input and output. Currently only 'simpleform' is implemented. See Data definition language (DDL) compiler module. As a real example we suggest to have a look at src/modules/ddlcompiler/simpleform/
  • Custom data type: Custom data types (_Wolframe::types::CustomDataType) define arithmetic types with some methods. The idea is to define arithmetic data types for things like date/time or currency only once and not for every language binding. Custom data types can be used in normalization programs and so in data forms to validate and normalize atomic elements. See Custom data type module. As a real example we suggest to have a look at src/modules/datatype/datetime/.
  • Normalization function: Normalization functions (_Wolframe::types::NormalizeFunction) are besides custom data types the basic bricks to define atomic data types in forms. This component type lets you define your own normalization functions. See Normalize function module. As a real example, that is using resources, have a look at src/modules/normalize/string. For an example, that is not using resources have a look at src/modules/normalize/string/.
  • Runtime environment: A runtime environment (_Wolframe::langbind::RuntimeEnvironment) is a configurable environment for functions that need a context for execution. The only case where a runtime environment is currently used in Wolframe is for .NET (Windows only). See Runtime environment host structure module. The only real example we have is .NET at src/modules/cmdbind/dotnet/.
  • Authenticator unit: An authenticator unit (_Wolframe::AAAA::AuthenticatorUnit) implements one or more authentication mechanisms. An authentication unit is chosen for authentication of the client if it is the first configured authentication unit in the configuration section AAAA that implements the mechanism chosen. The class processing the authentication is called authentication slice (_Wolframe::AAAA::AuthenticatorSlice). See Authenticator module. As a real example we suggest to have a look at tests/modules/authentication/fakeauth/.
  • Database interface: Wolframe has interfaces to execute queries on Sqlite3 and PostgreSQL databases. To define a new database interface, we have to implement the following interfaces: The database and the configuration are the objects you have to declare when implementing a database module. See Database interface module. As a real example have a look at src/modules/database/sqlite3/.

Defining a module configuration

For defining the configuration of a module we can either derive a class from _Wolframe::config::NamedConfiguration and implement the parsing by hand or we can derive from the class _Wolframe::serialize::DescriptiveConfiguration and declare the configuration in a descriptive way. The first method is mainly used in the core for not getting into a dependency to the serialization library (libwolframe_serialize). In modules we suggest to use the declarative way of describing a configuration. The declarative way lets you describe the basic configuration structure as class with atomic types (integer types, std::string, float and bool) or subclasses or arrays (std::vector) of one of them. Additionaly you can overwrite some hooks to do additional checks or transformations. Each class that is part of a declarative configuration, either as main structure or as substructure needs to have a static method with the following signature as member:

static const _Wolframe::serialize::StructDescriptionBase* getStructDescription();

The method has to return a description of the structure for introspection because nativ C++ does not provide introspection on its own. The template class _Wolframe::serialize::StructDescription offers some methods to assign names to data members and to tag them with properties (like the property optional). The example in Descriptive configuration declaration example describes a structure with a vector of substructures and some atomic elements.