Skip to content
Immortius edited this page Nov 30, 2019 · 10 revisions

Modules

A module is a package of code and assets that can be loaded at runtime. Each module is uniquely identified by a combination of an id and a version.

Module Types

There are three types of module that are supported by ModuleFactory out of the box:

Path Modules

Path modules are modules that exist as a directory - this is typically used to support modules under development. These are also the only type of module that support embedded libraries (by default in a /libs subdirectory).

Archive Modules

Archive modules are modules that exist as an archive file - either a jar, aar (for android) or zip file. This generally supports distributed modules. It is expected that these modules follow standard jar conventions for class placement.

Package Modules

A package module is a module that wraps a package on the classpath - it contains all the classes and resources in that package. This is intended to support "built-in" modules that are part of the application itself, and provides an easy way to expose classes and assets out of the application and into the module security sandbox.

Custom Modules

By instantiating modules directly using the Module constructor, it is possible to create more esoteric modules which are combinations of the above - such as covering multiple packages and pulling in resources from an archive.

Reflections Manifest

An important part of each module is a reflections manifest - this provides a listing of all classes and resources in the module with inheritance and annotation information. The manifest is generated using the Reflections Library. This can be done at runtime (on Java only, not Android) or preferably generated at compile time. Examples of this are here: java, android. By default ModuleFactory will load manifests named manifest.json, but it is also possible to register support for other manifest formats if desired.

Module Metadata

Each module has a ModuleMetadata object that provides the id and version, as well as other metadata describing the module. The built-in information is:

  • Id - the identifying name of the module. A module id is a Name - a case-insensitive string - so "core", "CORE", and "Core" are all equal Names.
  • Version - the identifying version of the module. Versions are Semantic Versions composed of three numbers - major, minor and patch - separated by periods. Some examples are 2.0.0, 1.7.3 and 4.1.3. Snapshot versions are also supported with a -SNAPSHOT suffix.
  • DisplayName - the internationalised name of the module suitable for display.
  • Description - the internationalised description of the module suitable for display.
  • Dependencies - information on what other modules are required by the module.
  • RequiredPermissions - any additional permissions that the module requires.

Dependencies is a list of other modules the module requires. Each module is qualified by an id and a range of supported versions - minVersion (inclusive) and maxVersion (exclusive). If not specified, the maxVersion is the next major version increment of the minVersion (so a minVersion of 1.2.3 would have a maxVersion of 2.0.0 by default).

Snapshot versions count as their version number, so a range of 1.2.3 to 2.0.0 would include 1.2.3-SNAPSHOT but not 2.0.0-SNAPSHOT. A real version is used over a snapshot version.

Dependencies can also be marked as Optional, either true or false defaulting to false. This indicates modules that aren't required, but if present must fall within the provided version range for the module to function.

By default ModuleFactory expect module metadata to be found in a file called Metadata is expected to be in a file called module.json in the root of each module.

Metadata File Format

Module metadata uses a json format that looks like this:

{
    "id": "Core",
    "version": "1.0.0",
    "author": "Immortius",
    "displayName": {
        "en": "Core"
    },
    "description": {
        "en": "A very important module"
    },
    "dependencies" : [
        {
            "id": "Base",
            "minVersion": "1.0.0",
            "maxVersion": "2.0.0",
            "optional": false
        }
    ]
}

This corresponds to the core metadata attributes. If you don't need to support multiple languages, displayName and description can be simplified to

{
    "displayName": "Core",
    "description": "A very important module"
}

Metadata Extensions

It is possible to register extensions to metadata - extra fields that will be read from the metadata files and become available in the ModuleMetadata of Modules. This is done by registering them with a ModuleMetadataReader and creating the ModulePathScanner with this reader:

ModuleMetadataReader metadataReader = new ModuleMetadataReader();
metadataReader.registerExtension("rating", Integer.class);
ModulePathScanner scanner = new ModulePathScanner(new ModuleLoader(metadataReader));

Extension data can then be read out using the same identifier as the extension was registered with:

Integer rating = module.getMetadata().getExtension("rating", Integer.class);

If the extension uses a more complicated object, a custom Gson deserializer or type adapter can be written and registered with the MetadataReader.

Loading Modules

Module Factory

ModuleFactory provides the ability to load modules from the file system or packages, finding and loading reflection manifests and module metadata. It is configured with default locations and formats for loading the different elements of modules - these can be customised as desired.

Module Path Scanner

ModulePathScanner is the recommended way of loading modules on bulk. By providing the scanner with a Module Registry and one or more paths, the scanner will find and load all modules within those paths and register them into the registry.

ModulePathScanner scanner = new ModulePathScanner();
ModuleRegistry registry = new TableModuleRegistry();
scanner.scan(registry, path, additionalPath);