Modules

Concepts

Module

A module is a collection of Java classes implementing de.ecm4u.faw.action.Action, Form or Action JSON, Textresources, and other resources that the FaW server can read from the Java classpath. It also contains JSON representations of database rows that have to be inserted into the database.

Every module has a name and a version. Only one version of a module can be installed into a FaW server although a version can be upgraded by installing a higher version.

When the FaW server starts the first for the first time after installing a version of a module by putting it on the classpath, the FaW server will recognize this and import the JSON represenations into the database. This can be used to insert new or update existing database rows like forms, tasks, actions, or textresources. This importing is only done once. Further restarts of the FaW server will recognize that the module version has already been imported and do nothing.

UUID

The JSON representation of database records uses UUIDs to identify rows in a portable way. Foreign keys are expressed using UUIDs, too.

Each database row must have a UUID which must not be changed. If the import logic encounters th JSON representation of a database row with an unknown UUID, it will insert the row into the database. If the UUID is alread known, the corresponding database row will be updated using the JSON represenation.

Timestamp

Upon first inserting a database row based on a JSON representation, the timestamp column of the database row will be set to the current timestamp. This applies to Tasks, Forms, and Textresources. The timestamp is used to decide if the row is old enough for a Flow to use. Only a row that is not newer than the creation timestamp of the Flow will be used. This prevents incompatible changes to affect existing Flow and Task. It guarantees that old instances will continue to use Tasks, Forms, and Textresources that where current at the time of their creation.

Upgrading a module to a newer version will never change the timestamps. If an incompatible change to a Task, Form, or Textresource must be made, a new JSON representation for it must be created and imported. The resulting row will receive the import time as its timestamp value. Only Flows and Tasks created after this import time will use it. Older instances will continue to use older versions.

Creating a Module

If you want to develop a new module, you should use the Module Build Tool. This tool simplifies the process of building a module.

A module is a JAR file with no dependecies.

Module YAML Configuration File

The Tasks that make up a module are defined in a YAML file called moduleConfig.yml.

JSON Representations

A module can contain any number of JSON files with representation of database rows. The simplest file is just an empty JSON object:

{}

It can contain objects for the types of database tables, all of them are optional. A valid but technically empty JSON file would be:

{
  "tasks": {},
  "actions": {},
  "actionPredecessors": {},
  "actionScripts": {},
  "forms": {},
  "textresources": {}
}

The objects keyed by tasks, actions, etc. there can be any number of JSON object representing a database row of the respective table. Each object is keyed by the UUID of the row. It contains fields that match the non-technical columns of the database table.

Example:

{
    "textresources": {
        "b1c6cfee-ec6f-4c31-9ccf-14b44319f13c": {
            "name": "sample.txt",
            "type": "text",
            "method": "inline",
            "value": "sample inline textresource",
            "module": "sample-1.0.8"
        }
    }
}

This represents a single row in the textresource table. Note that not for all columns values are given. Especially the id, deleted, uuid, and timestamp (where existing) columns must not be set in a JSON representation. The id, deleted, and timestamp are set by the server, the uuid column is defined by the JSON key.

The module Property

Every JSON object representing a row must have the property module. This is used to identify database rows that where imported from the same version of the same module.

It is recommended to set the module property to the same value for all JSON objects in a JSON file. It is further recommended to name the JSON file to match the module property used.

Example: The above textresource should be put into a file named sample-1.0.8.

Foreign Key Relations

Some tables have foreign key relations to other tables. The foreign keys are expressed using the UUIDs. For example, a task has actions:

{
    "tasks": {
        "7b8306e9-9945-4996-90e8-b7c5dcf588cc": {
            "name": "Eingangsdokument erfassen",
            "module": "sample-1.0.9"
        }
    },
    "actions": {
        "a1daac4c-d014-4bd1-a772-5eff48b7b877": {
            "task_uuid": "7b8306e9-9945-4996-90e8-b7c5dcf588cc",
            "name": "05-AssignAuthority",
            "implementation": "de.ecm4u.faw.api.impl.AssignAuthorityAction",
            "creation_order": 5,
            "params": "classpath:/de/ecm4u/faw/module/collectandstore/actions/05-AssignAuthority-1.2.12.json",
            "module": "sample-1.0.9"
        },
        "12d4a00d-a3bb-485d-a83c-d1d6404b394f": {
            "task_uuid": "7b8306e9-9945-4996-90e8-b7c5dcf588cc",
            "name": "10-CreateResource",
            "implementation": "de.ecm4u.faw.api.impl.CreateResourceAction",
            "creation_order": 10,
            "params": "{}",
            "module": "sample-1.0.9"
        }
    }
}

Note that the UUID of the task 7b8306e9-9945-4996-90e8-b7c5dcf588cc is referenced by the actions in the task_uuid field. The server will insert the proper foreign key relations when inserting or updating the database rows.

The same is done for the foreign key relation from actionPredecessors to actions.

Classpath Resources

Forms, actions, and textresources can be included inline in the JSON objects as text or as files in the module.

Forms

A form is defined in the definition JSON property. This property can contain the JSON definition of the form or it can reference a classpath resource.

Example 1:

{
    "forms": {
        "45ab69cc-15d5-4a54-b13f-61fb6d9b3829": {
            "name": "Pay Supplier Invoice",
            "definition": "{\"schema\": { \"properties\": { ... } }, ... }",
            "module": "sbcs-collect-and-store-1.2.7"
        }
    }
}

It is obvious that quotes in the inlined JSON must be escaped and that there can be no linebreaks to make it more readable. It is recommended to define the form in a JSON file and reference it in the definition property.

Example 2:

{
    "forms": {
        "45ab69cc-15d5-4a54-b13f-61fb6d9b3829": {
            "name": "Pay Supplier Invoice",
            "definition": "classpath:/de/ecm4u/faw/module/collectandstore/forms/Pay-Supplier-Invoice-1.2.7.json",
            "module": "sbcs-collect-and-store-1.2.7"
        }
    }
}

The leading classpath: is recognized at runtime and the form is loaded from the JSON file in the module. The file Pay-Supplier-Invoice-1.2.7.json must be saved as

/sr/main/resources/de/ecm4u/faw/module/collectandstore/forms/Pay-Supplier-Invoice-1.2.7.json

if Maven is used to build the module JAR.

Action Params

The parameters of an action are defined as a JSON object in the params property. As for forms, this property can be inlined or referenced as a file on the classpath.

Example 1:

{
    "actions": {
        "f387b0fe-e453-4338-9a52-3bc8a6a23aa5": {
            "task_uuid": "b58d3600-2398-49b0-be36-a4fd5d03183b",
            "name": "05-ReviewSupplierInvoice-AssignAuhtority",
            "implementation": "de.ecm4u.faw.api.impl.AssignAuthorityAction",
            "creation_order": 5,
            "params": "{    \"AssignAuthorityAction.assignee\": \"$authority\",    \"AssignAuthorityAction.watcher\": \"$watcher\"}",
            "module": "sbcs-collect-and-store-1.2.7"
        }
    }
}

Here the params property contains the action parameters as a JSON object that must be escaped. It is recommended to define the action parameters in a JSON file and reference it in the params property.

Example 2:

{
    "actions": {
        "a1daac4c-d014-4bd1-a772-5eff48b7b877": {
            "task_uuid": "7b8306e9-9945-4996-90e8-b7c5dcf588cc",
            "name": "05-AssignAuthority",
            "implementation": "de.ecm4u.faw.api.impl.AssignAuthorityAction",
            "creation_order": 5,
            "params": "classpath:/de/ecm4u/faw/module/collectandstore/actions/05-AssignAuthority-1.2.12.json",
            "module": "sbcs-collect-and-store-1.2.7"
        }
    }
}

The leading classpath: is recognized at runtime and the action parameters are loaded from the JSON file in the module. The file 05-AssignAuthority-1.2.12.json must be saved as

/sr/main/resources/de/ecm4u/faw/module/collectandstore/actions/05-AssignAuthority-1.2.12.json

if Maven is used to build the module JAR.

Textresources

Textresources can be inlined or loaded from the classpath or a local file. Loading a textresource from a local file is useful for configuration files that have to changed by the administrator. For textresources included in a module inlining them or loading them from the classpath is recommended.

Example 1:

{
    "textresources": {
        "a02a6822-1e67-4c71-a91a-16b6562333ad": {
            "name": "executed action",
            "type": "template",
            "method": "inline",
            "value": "${user} executed action '${action}' on this task",
            "module": "faw-base-1.0.1"
        }
    }
}

This textresource has the method inline, the value property contains the complete text.

Example 2:

{
    "textresources": {
        "c8deac78-5548-4ff8-8f86-fbe3b3e1e663": {
            "name": "ecm4u-lib.js",
            "type": "lib",
            "method": "url",
            "value": "classpath:/textresources/scripts/lib/ecm4u-lib.js",
            "module": "faw-base-1.0.0"
        }
    }
}

This textresource has the method url. The classpath: prefix in the value indicates that its text has to be read from the file ecm4u-lib.js on the classpath.

Upgrading a Module

Upgrading a module requires incrementing the version. The FaW server will only import the JSON representations included in a module if it hasn't done so. To make the FaW server import a new version, the version parameter of the @Module annotation must be incremented.

The version property of the Maven module should be incremented accordingly.

There are two kind of changes that can happen in an upgraded module: compatible and incompatible changes.

Making a Compatible Change

Compatible changes are changes that will not prevent existing tasks and flows to continue. Examples are bugfixes in Nashorn scripts, cosmetic changes to form layouts, added I18 labels. As soon as a change will break existing tasks or flows it is incompatible.

A compatible change can be made to an existing classpath resource. When the upgraded module is deployed, the FaW server will load the changed classpath resource at runtime.

JSON representations can also be changed, for example fixing the typo in an inlined form, action, or textresource. The FaW server will update the existing database rows using the updated JSON object. To identify existing rows the UUID is used. Note that the timestamp of an updated row is not changed. It is left at the value inserted at the time of the insertion of the row.

The names of JSON representation files changed only in compatible ways should not be changed. So even if the module has a higher version, for example 1.0.2, the name of an existing JSON representation file should still include the old version, for example 1.0.1. In the importFiles attribute of the @Module annotation the name of the JSON representation file must also be left unchanged.

Making an Incompatible Change

Incompatible changes are changes that, if used with or applied to existing tasks and flows, will break them. Examples are new form fields that scripts now depend on or changed logic in a Nashorn script that will not work with a task or form that already exists.

An incompatible change must not be made to an existing classpath resource. Instead, a copy of the classpath resource should be made and put onto the classpath using a new name.

Depending on the type of changed classpath resource, a new JSON representation must be included. For this a new JSON representation file should be created and added to the importFiles attribute of the @Module@ annotation.

Only add new JSON representations to such a new file using new UUIDs. If classpath resources are referenced by such a new JSON representation, only reference to new classpath resources added together with the JSON representation.

Never remove anything from a module. Keep all classpath resources and all JSON representations. This way the module will always include everything necessary for all tasks and flows created at various times in the past.

Usage of Version Numbers

Version numbers follow Sematic Versioning. They are used in the following way.

JSON Representation Filenames

The name of a JSON representation file should include the version that was current when the file was first added. It should not be changed later.

module Property of JSON Representations

The module property of JSON representation shall be build using the module name and the version that was current when the JSON representation was first added. It should not be changed later.

This and the previouse rule will effect in the name of the JSON representation file and all module properties in it being identical.

Classpath Resources Filenames

The filename of a classpath resources shall include the version that was current when the classpath resource was first added. It should not be changed later.

This and the previous two rules effect in the version in the classpath references in the JSON representations, the classpath resource filenames, and the name of the JSON respresentation file being identical.

Deploying a Module.

Put a Module JAR into the directory /usr/lib/ecm4u/sbcs-faw. Restart the server.

Unpacking a Module for Development

A Module JAR can be unpacked into the directory /usr/lib/ecm4u/sbcs-faw-extension. All classpath resources (scripts, JSON patches, ...) can be directly edited in the file system.

Make sure to include all changes made into the project that builds the JAR.