Skip to content

Gestalt Module Quick Start

Immortius edited this page Nov 26, 2019 · 11 revisions

Module Structure

There are three basic types of module:

  • A directory module is a directory on the file system.
  • An archive module is a packed file (e.g. zip, or jar/aar).
  • A package module is a the contents of a package on the classpath.

A directory module can have compiled code - this should be under /build/classes (by default). A directory module can also contain libraries in the `/libs' (by default).

Both types of modules must contain a module metadata file (named 'module.json' by default) at their root. This is a json file with the following format:

{
    "id": "Identifier",
    "version": "1.0.0",
    "displayName": {  
        "en": "Displayable name"
    },
    "description": {
        "en": "Longer description of the module"
    },
    "dependencies": [
        {
            "id": "BaseModule",
            "minVersion": "1.0.0",
            "maxVersion": "2.0.0",
            "optional": true 
        }
    ],
    "requiredPermissions" : ["network", "io"]
}

For dependencies, "maxVersion" is the upperbound (exclusive) for accepted versions. It is optional, and if not provided the next max version is the next major version - or next minor version if the version is below "1.0.0". Dependencies can be marked as optional if they are not required for a module to function, default is for dependencies to be mandatory.

For displayName and description, a simple string can also be provided instead of a mapping of languages - this will be used as the english text.

Required permissions is a list of additional permissions the module requires to function.

Create a Module Registry and populate with modules from a directory

A module registry is a store of available modules. The benefit of using a module registry over just a list or map is that it has extra capabilities around querying available versions of a module. This is used to enable dependency resolution.

A ModulePathScanner can discover archive and directory modules and add them to a registry.

import org.terasology.gestalt.module.ModulePathScanner;
import org.terasology.gestalt.module.ModuleRegistry;
import org.terasology.gestalt.module.TableModuleRegistry;

import java.io.File;

public ModuleRegistry buildModuleRegistry() {
    ModuleRegistry registry = new TableModuleRegistry();
    new ModulePathScanner().scan(registry, new File(""));
    return registry;
}

Determine a compatible set of dependencies for a selection of modules

DependencyResolver takes a registry and one or more required module names, and determines a compatible set of modules (if any). It will try to use the highest versions possible of the required modules, but will fall back to lower versions if necessary.

import org.terasology.gestalt.module.Module;
import org.terasology.gestalt.module.dependencyresolution.DependencyResolver;
import org.terasology.gestalt.module.dependencyresolution.ResolutionResult;
import org.terasology.gestalt.naming.Name;

import java.util.Set;

public Set<Module> determineModuleSet() {
    DependencyResolver resolver = new DependencyResolver(buildModuleRegistry());
    ResolutionResult resolutionResult = resolver.resolve(new Name("ModuleOne"), new Name("ModuleTwo"));
    if (resolutionResult.isSuccess()) {
        return resolutionResult.getModules();
    } else {
        throw new RuntimeException("Unable to resolve compatible dependency set for ModuleOne and ModuleTwo");
    }
}

Establish a sandboxed module environment

A module environment is a loaded set of modules. It provides access to the classes and resources in the module. Classes in modules are loaded in a sandbox which can be used to restrict their actions - the standard setup is to have a whitelist of classes and permissions that are allowed.

import org.terasology.gestalt.module.ModuleEnvironment;
import org.terasology.gestalt.module.sandbox.ModuleSecurityManager;
import org.terasology.gestalt.module.sandbox.ModuleSecurityPolicy;
import org.terasology.gestalt.module.sandbox.PermissionSet;
import org.terasology.gestalt.module.sandbox.StandardPermissionProviderFactory;
import java.security.Policy;

public ModuleEnvironment establishSecureModuleEnvironment() {
    StandardPermissionProviderFactory permissionProviderFactory = new StandardPermissionProviderFactory();

    // Establish standard permissions
    permissionProviderFactory.getBasePermissionSet().addAPIPackage("com.example.api");
    permissionProviderFactory.getBasePermissionSet().addAPIClass(String.class);

    // Add optional permission set "io"
    PermissionSet ioPermissionSet = new PermissionSet();
    ioPermissionSet.addAPIPackage("java.io");
    permissionProviderFactory.addPermissionSet("io", ioPermissionSet);

    // Installs a policy that relaxes permission access for non-module code
    Policy.setPolicy(new ModuleSecurityPolicy());
    // Installs the security manager to restrict access to permissions
    System.setSecurityManager(new ModuleSecurityManager());
    return new ModuleEnvironment(determineModuleSet(), permissionProviderFactory);
}

Discover classes from a module

Once an environment is established, it provides a number of methods for discovering classes of interest.

import org.terasology.gestalt.module.predicates.FromModule;

public void discoverTypes() {
    ModuleEnvironment environment = establishSecureModuleEnvironment();

    environment.getTypesAnnotatedWith(SomeAnnotation.class);
    environment.getTypesAnnotatedWith(SomeAnnotation.class, new FromModule(environment, new Name("ModuleId")));
    environment.getSubtypesOf(SomeInterface.class);
    environment.getSubtypesOf(SomeInterface.class, new FromModule(environment, new Name("ModuleId")));

    Name moduleId = environment.getModuleProviding(someDiscoveredClass);
}

Accessing files from a Module

ModuleEnvironments and Modules provide a listing ModuleFileSources for discovering and accessing resources contained in them. The ModuleEnvironment provides a composite view across all modules - this does mean resources with the same path and name can hide each other (the first module providing the resources when ordered by dependencies is discovered). However individual modules can be used to get the list of the specific resources they contain.

import java.nio.file.Path;
import java.nio.file.Paths;

public void accessFileInModule() {
        ModuleEnvironment environment = establishSecureModuleEnvironment();
        Optional<FileReference> bananaText = environment.getResources().getFile("fruits", "bananas.txt");
        Optional<FileReference> preferredBananaText = environment.get(new Name("Preferred")).getResources().getFile("fruits", "bananas.txt");
    }

Create a Module from a package

Often it is useful to create a module from code and resources in the classpath - for example, for a core engine module or for testing purposes.

public Module buildPackageModule() {
        // A module.json metadata file can also be used
        ModuleMetadata metadata = new ModuleMetadata();
        metadata.setId(new Name("Core"));
        metadata.setVersion(new Version("1.0.0"));
        ModuleFactory factory = new ModuleFactory();
        return factory.createPackageModule(metadata, "org.example.modules.engine");
    }