Skip to content

Gestalt Module Quick Start

Immortius edited this page Jun 8, 2015 · 11 revisions

Module Structure

A module is either a directory or an archive.

A directory module's classes should be under /build/classes (by default).

An archive may be a jar or zip.

Both types of modules must contain a module metadata file (named 'module.info' 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 major version is used - 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.

Create a Module Registry and populate with modules from a directory

A module registry is a store of all available modules.

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

public ModuleRegistry buildModuleRegistry() {
    ModuleRegistry registry = new TableModuleRegistry();
    registry.add(buildClasspathModule());
    new ModulePathScanner().scan(registry, Paths.get(""));
    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).

import org.terasology.module.DependencyResolver;
import org.terasology.module.ResolutionResult;

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

Modules can be run in a restricted environment where the classes and permissions they have access to are restricted.

import org.terasology.module.ModuleEnvironment;
import org.terasology.module.sandbox.BytecodeInjector;
import org.terasology.module.sandbox.ModuleSecurityManager;
import org.terasology.module.sandbox.ModuleSecurityPolicy;
import org.terasology.module.sandbox.StandardPermissionProviderFactory;

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

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

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

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

In addition to manually populating the security manager's api, the APIScanner can be used to populate it with all @API annotated classes and packages from classpath modules:

new APIScanner(permissionProviderFactory).scan(buildModuleRegistry());

Discover classes from a module

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

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

    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 provide virtual file systems that allow the internal file structure of their modules to be explored regardless of whether they are an archive or directory.

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

public void accessFileInModule() {
    ModuleEnvironment environment = establishSecureModuleEnvironment();
    // Obtain a path to "banana.txt" in the "fruits" subdirectory of the "core" module 
    Path dataFilePath = environment.getFileSystem().getPath("core", "fruits", "banana.txt");
    // ... Work with dataFilePath as any other path ...
}

Create a Module for a library on the Classpath

Often it is good to have some part (or all) of the classpath to act as a module.

import org.terasology.module.Module;
import org.terasology.module.ClasspathModule;
import org.terasology.module.ModuleMetadata;
import org.terasology.naming.Name;
import org.terasology.naming.Version;

public Module buildClasspathModule() {
    ModuleMetadata metadata = new ModuleMetadata();
    metadata.setId(new Name("Core"));
    metadata.setVersion(new Version("1.0.0"));
    try {
        return ClasspathModule.create(metadata, getClass());
    } catch (URISyntaxException e) {
        throw new RuntimeException("Source location could not be converted to URI", e);
    }
}

Clone this wiki locally