-
Notifications
You must be signed in to change notification settings - Fork 22
Gestalt Module Quick Start
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.
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;
}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");
}
}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());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);
}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 ...
}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);
}
}