{"id":50436725,"url":"https://github.com/techsenger/weaverbird","last_synced_at":"2026-05-31T17:03:00.573Z","repository":{"id":296624507,"uuid":"940235593","full_name":"techsenger/weaverbird","owner":"techsenger","description":"Techsenger Weaverbird – A framework for dynamically creating and deploying modular components using ModuleLayer.","archived":false,"fork":false,"pushed_at":"2026-05-24T08:02:37.000Z","size":952,"stargazers_count":16,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-24T10:08:28.261Z","etag":null,"topics":["java","java-9-module","java-9-modules","java-framework","java-lib","java-library","java-modularity","java-module","java-module-standalone","java9","java9-jigsaw","jigsaw","jpms","modularization","modules"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/techsenger.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"techsenger"}},"created_at":"2025-02-27T20:44:15.000Z","updated_at":"2026-05-24T08:02:41.000Z","dependencies_parsed_at":"2025-06-01T10:49:40.176Z","dependency_job_id":"301aa0f8-d8f9-4f45-9390-9be4efcdc1ce","html_url":"https://github.com/techsenger/weaverbird","commit_stats":null,"previous_names":["techsenger/alpha","techsenger/weaverbird"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/techsenger/weaverbird","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techsenger%2Fweaverbird","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techsenger%2Fweaverbird/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techsenger%2Fweaverbird/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techsenger%2Fweaverbird/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/techsenger","download_url":"https://codeload.github.com/techsenger/weaverbird/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/techsenger%2Fweaverbird/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33739861,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["java","java-9-module","java-9-modules","java-framework","java-lib","java-library","java-modularity","java-module","java-module-standalone","java9","java9-jigsaw","jigsaw","jpms","modularization","modules"],"created_at":"2026-05-31T17:02:59.914Z","updated_at":"2026-05-31T17:03:00.562Z","avatar_url":"https://github.com/techsenger.png","language":"Java","funding_links":["https://github.com/sponsors/techsenger"],"categories":[],"sub_categories":[],"readme":"# Techsenger Weaverbird\n\nTechsenger Weaverbird is a framework built on top of the Java Platform Module System (JPMS) that manages modular components\nthrough dynamic module layers. It provides a powerful API and versatile interfaces (CLI/GUI) with multiple built-in\ncommands — helping developers efficiently build and manage modular systems.\n\n## Table of Contents\n* [Overview](#overview)\n* [Use Cases](#use-cases)\n* [Demo](#demo)\n    * [CLI Demo](#demo-cli)\n    * [GUI Demo](#demo-gui)\n* [Usage](#usage)\n    * [Framework](#usage-framework)\n        * [Directory Layout](#usage-framework-directory)\n        * [Registry](#usage-framework-registry)\n        * [Boot](#usage-framework-boot)\n    * [Component](#usage-component)\n        * [Life Cycle](#usage-component-life)\n        * [Activator](#usage-component-activator)\n        * [Configuration](#usage-component-config)\n        * [Events](#usage-component-events)\n        * [Services](#usage-component-services)\n    * [Remote Control](#usage-remote-control)\n    * [Text Commands](#usage-commands)\n    * [CLI](#usage-cli)\n    * [GUI](#usage-gui)\n        * [Console](#usage-gui-console)\n        * [Memory Log](#usage-gui-memory-log)\n        * [Diagrams](#usage-gui-diagrams)\n    * [Assembly Maven Plugin](#usage-assembly-plugin)\n* [Requirements](#requirements)\n* [Dependencies](#dependencies)\n* [Code building](#code-building)\n* [Running Demo](#running-demo)\n* [License](#license)\n* [Contributing](#contributing)\n* [Support Us](#support-us)\n\n## Overview \u003ca name=\"overview\"\u003e\u003c/a\u003e\n\nJPMS (Java Platform Module System), which was introduced in Java 9, along with modules, added the concept of module\nlayer. A layer can be defined as a group of modules that are loaded and managed together. Key features of a layer\ninclude:\n\n* Isolation - modules within a layer can be independent of other layers.\n* Hierarchy - layers form a graph and can use modules from parent layers.\n* Dynamic configuration - layers can be dynamically added and removed (except the boot layer).\n\nTechsenger Weaverbird is a framework designed to work with module layers. The framework resides in the boot layer and\nhandles all the work of managing the other layers. To facilitate this, the concept of a component is introduced.\n\nA component is a logical part of the system that can be dynamically added or removed. Each component is deployed in a\nseparate module layer and has a clearly defined lifecycle. The configuration of a component is specified via a `Builder`\nor an XML, which describes the component's modules (groupId, artifactId, version), module directives\n(opens, reads, exports, etc.), repositories from which modules can be loaded, and other related information.\nFor flexibility, the XML configuration supports properties, if and choose-when constructs, and EL (Expression Language).\n\nAll loaded modules are stored in the framework's internal repository, which is by default a Maven repository.\n\nThe code within a component is started and stopped using activators — special services invoked by the framework during\nthe activation or deactivation of the component.\n\nYou can interact with the framework either through the API or through the text command mechanism. Text commands are a\nconvenient way to work with the framework. By default, about 40 commands are provided for working with components,\nsessions, gathering information, etc. At the same time, it is very easy to add your own commands. The framework will\nautomatically discover them in any layer (the module must provide a factory service) and integrate them into any\nconsole. The framework can also execute command scripts, which consist of a series of commands, for example, from a file.\n\nThe framework provides two types of consoles: CLI and GUI. The CLI console allows working with text commands. The GUI\nconsole additionally allows working with logs and generates diagrams with information about layers, modules, and\npackages. These diagrams are especially useful when working with complex systems.\n\n## Use Cases \u003ca name=\"use-cases\"\u003e\u003c/a\u003e\n\nThe framework can be used for programs that:\n\n* Have subsystems that can be dynamically added or removed.\n* Support plugins, extensions, add-ons, etc., that can be dynamically loaded.\n* Include a web server and web applications, where each web application is a module.\n* Use modules that are loaded based on conditions, such as the operating system type, etc.\n\n## Demo \u003ca name=\"demo\"\u003e\u003c/a\u003e\n\n### CLI Demo \u003ca name=\"demo-cli\"\u003e\u003c/a\u003e\n\n\u003cimg width=\"1209\" height=\"811\" alt=\"Weaverbird CLI\" src=\"https://github.com/user-attachments/assets/f6dfcc1d-a674-45cd-89e6-fa5ed7a5b6ee\" /\u003e\n\n### GUI Demo \u003ca name=\"demo-gui\"\u003e\u003c/a\u003e\n\n\u003cimg width=\"1200\" height=\"800\" alt=\"Weaverbird GUI\" src=\"https://github.com/user-attachments/assets/55986da2-01e0-4c8e-bb85-bec9a02bf2ef\" /\u003e\u003cbr\u003e\n\n\u003cimg width=\"1200\" height=\"800\" alt=\"Weaverbird GUI layer diagram\" src=\"https://github.com/user-attachments/assets/ac9cf773-7615-47f0-8b57-8d58df260e49\" /\u003e\u003cbr\u003e\n\n\u003cimg width=\"1200\" height=\"800\" alt=\"Weaverbird GUI module diagram\" src=\"https://github.com/user-attachments/assets/c02dabee-320f-4723-a767-4ecf0d45628c\" /\u003e\u003cbr\u003e\n\n\u003cimg width=\"1200\" height=\"800\" alt=\"Weaverbird GUI dialog\" src=\"https://github.com/user-attachments/assets/f367ef3b-c938-4c27-afa5-f221e85b2b3e\" /\u003e\n\n## Usage \u003ca name=\"usage\"\u003e\u003c/a\u003e\n\n### Framework \u003ca name=\"usage-framework\"\u003e\u003c/a\u003e\n\nThe framework is always located in the root layer. In production, this is the boot layer, but in tests, it can also be\na dynamically created layer. To work with the framework, you need to use the `Framework` class, which provides all the\nnecessary tools.\n\nIt is important to note that when launching the framework, the parameter `--add-modules ALL-DEFAULT` is used, which\nloads all default JRE/JDK modules into the boot layer. This is necessary because JRE/JDK modules can only be added to\nthe boot layer.\n\n#### Directory Layout \u003ca name=\"usage-framework-directory\"\u003e\u003c/a\u003e\n\nTo use the framework in the most efficient way, it is important to carefully design the folder and file structure.\nThis responsibility is handled by two classes: `PathManager` and `PathResolver` (to create a framework with a custom\n`PathManager`, use `FrameworkFactory.create(settings, pathManager)`).\n\n`PathManager` defines paths to the main directories used by the platform, such as `bin`, `cache`, `data`, `repo`, etc.\nThe `PathResolver`, which can be obtained via `PathManager#getPathResolver()`, is responsible for resolving paths for\na specific component.\n\nThe need for `PathResolver` arises from the fact that each component may have not only its own configuration, but\nalso its own cache, data, documentation, and other resources. Therefore, it is necessary to separate files of\ndifferent components within shared directories where component data is stored. Such directories may include:\n`cache`, `config`, `data`, `doc`, `legal`.\n\nBy default (`DefaultPathResolver`), the framework organizes these directories as follows:\n\nAt the root of the folder, framework files are located, while component files are organized into two nested folders.\nThe name of the first folder is the component name, and the name of the second folder is the component version,\nsee `PathResolver`.\n\nFor example:\n\n```\nconfig\n│── Component A\n│   │── 1.0.0\n│   │   │── configuration.xml   //component file\n│   │   │── settings.xml        //component file\n│── log4j2.xml                  //framework file\n```\n\n#### Registry \u003ca name=\"usage-framework-registry\"\u003e\u003c/a\u003e\n\nThe framework stores data about the installation and components in a registry, which is located in the file\n`data/weaverbird-registry.xml`. If the client and server are located in the same folder, as in the `weaverbird-demo-net` example,\nboth the client and server instances of the framework use this file.\n\n#### Boot \u003ca name=\"usage-framework-boot\"\u003e\u003c/a\u003e\n\nThe boot layer must always contain a minimal set of modules: the two framework modules, the Expression Language modules,\nand the logging modules. In addition, the boot layer must contain a module that launches and initializes the framework\n— in a distribution this is the main application module, and in integration tests this is the test module itself.\n\nThere are two ways to populate the boot layer with the required modules. If the JVM is launched via `.sh`/`.bat`\nscripts, the module path is configured by the scripts automatically. The `assemble-dist` goal of the Assembly Maven\nPlugin handles this by generating the scripts with the correct module path.\n\nIf the JVM is launched via Maven plugins (`exec-maven-plugin`, `maven-failsafe-plugin`, etc.), Maven takes care of\nadding the required modules to the boot layer, which makes it convenient both for running the application and for\nsetting up the integration test environment.\n\n### Component \u003ca name=\"usage-component\"\u003e\u003c/a\u003e\n\nEach component has a name and a version. The name and version are separated by `:`. For example, the foo component\nwith version 1.0.0 is specified as `foo:1.0.0`.\n\nAn unlimited number of instances of the same component can be created, as instances are identified in the system by\ntheir `id`. Additionally, a component instance can be assigned an `alias` and accessed using this `alias`. This is\nespecially useful when referencing a component instance in command scripts.\n\n#### Life Cycle \u003ca name=\"usage-component-life\"\u003e\u003c/a\u003e\n\nEach component can have the following states: `added`, `resolved`, `deployed`, `activated`.\n\n**Added**. The component has been added, but its modules have not yet been loaded from external repositories. The\ncomponent can be added either manually or automatically.\n\nTo manually add a component, the following steps need to be performed:\n\n1. Create the component's XML configuration.\n2. Place the configuration in the special `config` folder.\n3. Add the component to the AddedComponents registry of the framework.\n\nThe automatic addition of a component occurs when there is a component ZIP archive (with any extension).\n\nThe component archive is used for distributing the component. For example, if a program using the framework works with\nplugins, each plugin is distributed via its archive. The component archive must always contain the component's\nconfiguration. Additionally, it may contain JAR/WAR modules (which are not in the repository), component data, etc.\n\nThe component archive can be created using the `component:build` command, and the component can be added using the\n`component:add` command.\n\n**Resolved**. The component's modules have been loaded into the framework's repository, and the component can be deployed.\nThe resolution of the component is performed using the `component:resolve` command.\n\n**Deployed**. A `ModuleLayer` has been created for the component, which contains all of the component's modules. However,\nthe component's code has not yet been executed. The deployment of the component is performed using the\n`component:deploy` command.\n\n**Activated**. All activators of the component's active modules have been called. The component's code is now being\nexecuted. The activation of the component is performed using the `component:activate` command.\n\nThe commands mentioned above allow working with each component state individually; however, in many cases, this is\nunnecessary. Therefore, there are combined commands that handle multiple states at once. For example, instead of using\nthe `component:add` and `component:resolve` commands separately, you can use a single command, `component:install`.\n\nTable of commands and states:\n\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eInitial State\u003c/td\u003e\n        \u003ctd\u003eFinal State\u003c/td\u003e\n        \u003ctd\u003eCommand\u003c/td\u003e\n        \u003ctd\u003eCombined Command\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e—\u003c/td\u003e\n        \u003ctd\u003eAdded\u003c/td\u003e\n        \u003ctd\u003ecomponent:add\u003c/td\u003e\n        \u003ctd rowspan=\"2\"\u003ecomponent:install\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eAdded\u003c/td\u003e\n        \u003ctd\u003eResolved\u003c/td\u003e\n        \u003ctd\u003ecomponent:resolve\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eResolved\u003c/td\u003e\n        \u003ctd\u003eDeployed\u003c/td\u003e\n        \u003ctd\u003ecomponent:deploy\u003c/td\u003e\n        \u003ctd rowspan=\"2\"\u003ecomponent:start\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eDeployed\u003c/td\u003e\n        \u003ctd\u003eActivated\u003c/td\u003e\n        \u003ctd\u003ecomponent:activate\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eActivated\u003c/td\u003e\n        \u003ctd\u003eDeployed\u003c/td\u003e\n        \u003ctd\u003ecomponent:deactivate\u003c/td\u003e\n        \u003ctd rowspan=\"2\"\u003ecomponent:stop\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eDeployed\u003c/td\u003e\n        \u003ctd\u003eResolved\u003c/td\u003e\n        \u003ctd\u003ecomponent:undeploy\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eResolved\u003c/td\u003e\n        \u003ctd\u003eAdded\u003c/td\u003e\n        \u003ctd\u003ecomponent:unresolve\u003c/td\u003e\n        \u003ctd rowspan=\"2\"\u003ecomponent:uninstall\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003eAdded\u003c/td\u003e\n        \u003ctd\u003e—\u003c/td\u003e\n        \u003ctd\u003ecomponent:remove\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\n#### Activator \u003ca name=\"usage-component-activator\"\u003e\u003c/a\u003e\n\nActivators are module services that are invoked when a component is activated or deactivated. To create an activator,\nfollow these steps:\n\n1. Implement the `ModuleActivator` interface in the module.\n2. In the `module-info`, add `provides ... with ...`.\n\nImportant! The created activator will not be called if the configuration of the component does not specify that the\nmodule is active:\n\n```\n\u003cModule groupId=\"...\" artifactId=\"...\" version=\"...\" active=\"true\"/\u003e\n```\nThus, you can control through the component's configuration whether the module activators should be triggered or not.\n\nWhen the component is activated, its activators are called in the order in which they are specified in the component's\nconfiguration. During deactivation, they are called in reverse order (the last active module is deactivated first).\n\n#### Configuration \u003ca name=\"usage-component-config\"\u003e\u003c/a\u003e\n\nThe configuration can be set using a `Builder` or an XML.\n\n```java\nvar config = ComponentConfig.builder()\n        .name(\"foo\")\n        .version(Version.of(\"1.0.0\"))\n        .metadata(\"License\", \"Apache 2\")\n        .repositories(\n            r -\u003e r.name(\"local\").url(\"file://....\"),\n            r -\u003e r.name(\"central\").url(\"https://repo1.maven.org/maven2/\")\n        )\n        .parents(\n            p -\u003e p.name(...).version(...).versionMatch(...)\n        )\n        .modules(\n            m -\u003e m.groupId(...).artifactId(...).version(...).classifier(...).active(...)\n                .directives(\n                    d -\u003e d.type(...).pkg(...).layer(...).module(...)\n                )\n        )\n        .build();\n```\n\nA XML configuration template with all supported tags:\n\n```xml\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\" ?\u003e\n\u003cConfiguration name=\"foo\" version=\"1.0.0\" type=\"notBar\"\u003e\n    \u003cMetadata\u003e\n        \u003cEntry key=\"License\" value=\"Apache 2\"/\u003e\n    \u003c/Metadata\u003e\n\n    \u003cRepositories\u003e\n        \u003cRepository name=\"local\" url=\"file://....\"/\u003e\n        \u003cRepository name=\"central\" url=\"https://repo1.maven.org/maven2/\"/\u003e\n    \u003c/Repositories\u003e\n\n    \u003cParents\u003e\n        \u003cParent name=\"... \" version=\"...\" versionMatch=\"major/minor/patch\"/\u003e\n    \u003c/Parents\u003e\n\n    \u003cChoose\u003e\n        \u003cWhen test=\"${info['os.family'] == 'linux'}\"\u003e\n            \u003cProperty name=\"modVersion\" value=\"2.0.0\"/\u003e\n        \u003c/When\u003e\n        \u003cWhen test=\"${info['os.family'] == 'mac'}\"\u003e\n            \u003cProperty name=\"modVersion\" value=\"3.0.0\"/\u003e\n        \u003c/When\u003e\n    \u003c/Choose\u003e\n\n    \u003cModules\u003e\n        \u003cIf test=\"${...}\"\u003e\n            \u003cModule groupId=\"...\" artifactId=\"...\" version=\"...\"/\u003e\n        \u003c/If\u003e\n\n        \u003cModule groupId=\"...\" artifactId=\"...\" version=\"${config['modVersion']}\" active=\"true\" nativeAccessEnabled=\"true\"\u003e\n            \u003cDirectives\u003e\n                \u003cDirective type=\"opens/reads/exports\" package=\"...\" layer=\"...\" module=\"...\"/\u003e\n                \u003cDirective type=\"requestsOpen/requestsRead/requestsExport\" layer=\"...\" module=\"...\" package=\"...\"/\u003e\n            \u003c/Directives\u003e\n        \u003c/Module\u003e\n    \u003c/Modules\u003e\n\u003c/Configuration\u003e\n```\n\nFor detailed information on the component configuration, refer to the `ComponentConfig` Javadoc. Here, we will\nonly cover the key points that should be noted.\n\n**Expression Language**. All attributes can have EL expressions. By default, five maps are defined in the EL context:\n\n```\nsys — System.getProperties()\nenv — System.getenv()\nconfig — This configuration properties\ninfo — ComponentManager.getConfigInfo()\nutils — ComponentManager.getConfigUtils\n```\n\nThe last two maps are accessible through the `ComponentManager` and can be modified if necessary.\n\n**Module Directive**. The module directives can be divided into two groups. The first group includes directives\nspecified in the `module-info`. The second group includes directives specified in the configuration.\n\nUsing directives in the configuration is necessary because, during module development, it is impossible to foresee\nall possible use cases for the module. For example, if a module uses the API of a specification, it generally does\nnot know which implementation of that specification will be used. Therefore, when a particular implementation is used,\nit may be necessary to add extra directives.\n\nThe layer attribute allows specifying the module's layer. If this attribute is not specified, the layer of this\ncomponent is used. To specify the framework and component layer, either the name `foo` or the name and version\n`foo:1.0.0` is used. The name of the layer where the framework is located is `weaverbird-framework`.\n\nThe difference between the directives `opens`, `reads`, `exports` and the directives `requestsOpen`, `requestsRead`,\n`requestsExport` is that the former are applied directly to the module specified in the `Module` tag, while the latter\nare applied to the module specified in the `module` attribute. In other words, the first set of directives is applied\ndirectly to the configured module, while the second set is applied to another module, usually from the parent layer.\nFor example, such a configuration:\n\n```\n\u003cDirective type=\"requestsRead\" layer=\"foo\" module=\"bar\"/\u003e\n```\nwill result in the `reads` directive being added to the `bar` module from `foo` layer.\n\nIt is important to note that directives are added through the layer controller. Since JPMS does not provide access\nto the boot layer controller, `requests` directives cannot be applied to modules of the boot layer via the layer\ncontroller.\n\nHowever, this limitation can be worked around using a relay approach: the boot layer module opens its package to the\nframework core module (configured via `--add-opens` at JVM startup), and since all layers are created by the core\nmodule, it relays the access further to the target module using `Module.addOpens()`. For example, the following\nconfiguration will open `java.time` from `java.base` to Gson:\n\n```xml\n\u003cModule groupId=\"com.google.code.gson\" artifactId=\"gson\" version=\"${bom.gson.version}\"\u003e\n    \u003cDirectives\u003e\n        \u003cDirective type=\"requestsOpen\" layer=\"weaverbird-framework:${project.version}\" module=\"java.base\" package=\"java.time\"/\u003e\n    \u003c/Directives\u003e\n\u003c/Module\u003e\n```\n\nFor a specific example of working with directives, see the configuration of the demo component — `weaverbird-gui`.\n\n#### Events \u003ca name=\"usage-component-events\"\u003e\u003c/a\u003e\n\nThe framework allows receiving events related to components. For this, `ModuleActivator` receives a `ModuleContext`,\nfrom which a reference to the `Component` can be obtained. The `Component` allows adding a `ComponentObserver`. To\nimplement an observer, it is generally easier to use `AbstractComponentObserver`, which provides empty implementations\nfor all methods.\n\n#### Services \u003ca name=\"usage-component-services\"\u003e\u003c/a\u003e\n\nFinding services within a single layer or in its parent layers is not an issue. However, when searching for services\nthat may not be in parent layers but instead in child layers or even outside the hierarchy, the task becomes more\ncomplex. The reason for this is that such layers can be dynamically added and removed.\n\nThere are two solutions to this problem:\n\n1. Use `ServiceTracker`.\n2. Use `ComponentObserver`.\n\n### Remote Control \u003ca name=\"usage-remote-control\"\u003e\u003c/a\u003e\n\nThe framework provides out-of-the-box support for remote management over HTTP. To understand how to work with it, you\ncan refer to the integration test configuration in the `weaverbird-it-net` module.\n\nIt is important to note that the data channel is not encrypted. When deploying the client and server on different\nhosts, you must ensure traffic protection using a VPN or an SSH tunnel.\n\n### Text Commands \u003ca name=\"usage-commands\"\u003e\u003c/a\u003e\n\nText commands are a powerful tool for working with the framework; however, their use is optional. It is important\nto note that all commands, both built-in and custom, are automatically added to the CLI and GUI consoles.\n\nAll text-based commands are transmitted to the platform over the HTTP network protocol. On one hand, this introduces\na small overhead when the client and server run within the same JVM instance; on the other hand, this approach\nallows interaction with different servers.\n\n**Local and Remote Commands.** Any command can be local, remote, or both. Local commands can be executed without a\nsession, while remote commands require an active session.\n\n**Command Executor**. The execution of commands is handled by `CommandExecutor`, which receives a `String` containing\nthe commands as input. Each command is a separate class implementing the `Command` interface. The executor splits the\ntext into individual commands and processes each one.\n\nThe execution flow of a local command is as follows:\n\n```\nConsole ⮂ Command Executor ⮂ Command ⮂ Framework API\n```\n\nThe execution flow of a remote command is as follows:\n\n```\nConsole ⮂ Command Executor ⮂ Command ⮂ Client ⮂ Server ⮂ EndpointHandler ⮂ Framework API\n```\n\n**Custom Command**. To create a custom command, you need to do the following:\n\n1. Implement the `Command` interface (it is recommended to inherit from `AbstractCommand`).\n2. Create provider for `CommandService` in the module containing the command.\n3. Add the module to the console component.\n\nTo create a custom `EndpointHandler`, do the following:\n1. Implement the `EndpointHandler` interface.\n2. Create provider for `EndpointHandlerService` in the module containing the handler.\n3. Add the module to the server component.\n\n**Command Scripts**. A command `String` can contain an unlimited number of commands separated by `;`. This allows for\nthe use of command scripts — files that contain text commands. Typically, scripts are stored in the `script` folder.\n\n**Special Symbols**. When working with commands, the following special characters should be considered:\n\n* `;` — used to separate commands.\n* `#` — used for comments in scripts. It can only be the first character of a line.\n* `!` — used as a prefix for commands when working in sessions, to execute them as local commands.\n\n**Basic Commands**\n\n```\n# to open a session to the server\nsession:open demo -a 127.0.0.1:1200 -n admin -p admin\n\n# to list all remote commands\ncommand:list\n\n# to list all local commands\n!command:list\n\n# to detach the session\n!session:detach\n```\n\n### CLI \u003ca name=\"usage-cli\"\u003e\u003c/a\u003e\n\nThe CLI is represented by a console that allows you to enter commands and parameters to interact with the framework.\nThe console supports command history, autocomplete (for both commands and parameters via the Tab key), and command\nhighlighting.\n\nUnlike the GUI, the CLI is limited to executing commands. Its strength lies in its versatility — it can operate in\nenvironments without a graphical interface.\n\n### GUI \u003ca name=\"usage-gui\"\u003e\u003c/a\u003e\n\nThe GUI offers significantly more capabilities compared to the CLI. While the CLI is limited to a single console,\nthe GUI provides three components: Console, Memory Log, and Diagrams — allowing you not only to execute commands but also\nto view log messages and generate diagrams for working with layers, modules, and packages.\n\n#### Console \u003ca name=\"usage-gui-console\"\u003e\u003c/a\u003e\n\nThe Console is a component that allows executing text commands. It is similar to the CLI console with two main differences:\n\n1. To invoke the completer, you need to use the `Ctrl` + `Space` keyboard shortcut.\n2. It provides a graphical interface for working with sessions.\n\n#### Memory Log \u003ca name=\"usage-gui-memory-log\"\u003e\u003c/a\u003e\n\nThe memory log stores all log events that occur in the system. Since it receives all messages directly from `log4j2`\nrather than reading them from a file, it offers greater flexibility for viewing and filtering those messages.\n\nCurrently, the memory log retains all messages until it is cleared, so it cannot be used in production. It is\nintended solely for testing and debugging purposes.\n\nBy default, the memory log is not active. To enable it, you must set the parameter\n`com.techsenger.weaverbird.core.log.memory` to `true` in the `.sh` / `.bat` scripts.\n\n#### Diagrams \u003ca name=\"usage-gui-diagrams\"\u003e\u003c/a\u003e\n\nDiagrams are a useful tool that provides complete information about layers, modules, and packages. The importance of\nthis tool is especially heightened when working with complex systems.\n\nDiagrams have their own settings, which can be viewed in the `Settings` dialog.\n\n## Assembly Maven Plugin \u003ca name=\"usage-assembly-plugin\"\u003e\u003c/a\u003e\n\nThe Assembly Maven Plugin is a key tool for working with the framework, as it handles all the work of assembling the\nframework both for integration tests and distributions. It creates the initial directory structure with the repository\npopulated with the required modules and the necessary configuration files. The generated structure follows the standard\nframework layout with `bin`, `config`, `repo`, and other directories. The `bin` directory contains `.sh` and `.bat`\nscripts with the specified main class.\n\nThe plugin allows specifying custom modules that need to be added to the repository at the framework assembly stage.\nTypically, this applies to modules that must reside in the boot layer, for example, if a module contains the framework\nstartup code. For such modules, `\u003conModulePath\u003e` must be set to `true`.\n\nIt is important to note that if a module is used exclusively for components, it only needs to be specified in the\nplugin configuration in special cases where the module cannot be resolved from any repository (including the local one)\nduring component resolution. In other words, as a rule, component modules do not need to be specified in the plugin\nconfiguration.\n\nThe plugin provides the following goals:\n\n* `assemble-runtime` — used for integration testing. It produces a minimal set of files required to run the framework.\n  The execution is skipped if the specified `path` already exists.\n* `assemble-dist` — creates a full distribution, including `.sh`/`.bat` scripts and the default Log4j2 configuration.\n  The execution is skipped if the specified `path` already exists.\n* `update` — intended for development purposes. It updates the repository with the specified modules in an existing\n  distribution, avoiding full reassembly on every change. The execution is skipped if the specified `path` doesn't exist.\n\nCommon configuration parameters for `assemble-runtime` and `assemble-dist` goals:\n\n| Parameter | Required | Default | Description |\n|-----------|----------|---------|-------------|\n| `path` | Yes | - | Path to the root framework directory where the runtime/distribution will be assembled |\n| `components` | Yes | - | The list of components that will be added to the runtime/distribution |\n| `components/component` | Yes | - | The component that will be added to the runtime/distribution |\n| `modules` | No | - | The list of additional JPMS modules to resolve and include in the repository in addition to the minimal set of required modules. |\n| `modules/module` | Yes | - | The JPMS module to resolve and include in the repository in addition to the minimal set of required modules. |\n| `module/groupId` | Yes | - | Module group ID |\n| `module/artifactId` | Yes | - | Module artifact ID |\n| `module/version` | Yes | - | Module version |\n| `module/type` | No | `jar` | Module type |\n| `module/classifier` | No | - | Module Classifier |\n\nAdditional configuration parameters for the `assemble-dist` goal:\n\n| Parameter | Required | Default | Description |\n|-----------|----------|---------|-------------|\n| `mainClass` | Yes | - | Main class in the format `module/fully.qualified.ClassName`, used only in `.sh`/`.bat` scripts|\n| `scriptName` | No | `framework` | The name of the `.sh`/`.bat` scripts|\n| `jvmArgs` | No | - | The JVM arguments used in `.sh`/`.bat` scripts|\n| `jvmArgs/jvmArg` | Yes | - | The JVM argument used in `.sh`/`.bat` scripts|\n| `module/onModulePath` | No | `false` | If `true`, the module will be added to the `--module-path` in `.sh`/`.bat` scripts |\n\nConfiguration parameters for `update` goal:\n\n| Parameter | Required | Default | Description |\n|-----------|----------|---------|-------------|\n| `path` | Yes | - | Path to the root framework directory where the modules will be updated |\n| `modules` | No | - | The list of modules in the repository to update. |\n| `modules/module` | Yes | - | The module in the repository to update. |\n| `module/groupId` | Yes | - | Module group ID |\n| `module/artifactId` | Yes | - | Module artifact ID |\n| `module/version` | Yes | - | Module version |\n| `module/type` | No | `jar` | Module type |\n| `module/classifier` | No | - | Module Classifier |\n\nExample:\n\n```xml\n\u003cplugin\u003e\n    \u003cgroupId\u003ecom.techsenger.weaverbird.assembly\u003c/groupId\u003e\n    \u003cartifactId\u003eweaverbird-assembly-maven-plugin\u003c/artifactId\u003e\n    \u003cversion\u003e${weaverbird.version}\u003c/version\u003e\n    \u003cexecutions\u003e\n        \u003cexecution\u003e\n            \u003cphase\u003everify\u003c/phase\u003e\n            \u003cgoals\u003e\n                \u003cgoal\u003eassemble-dist\u003c/goal\u003e\n            \u003c/goals\u003e\n            \u003cconfiguration\u003e\n                \u003cpath\u003e${project.build.directory}/framework\u003c/path\u003e\n                \u003cmainClass\u003ecom.myapp/com.myapp.Main\u003c/mainClass\u003e\n                \u003cjvmArgs\u003e\n                    \u003cjvmArg\u003e-agentlib:jdwp=transport=dt_socket,address=7700,server=y,suspend=n\u003c/jvmArg\u003e\n                \u003c/jvmArgs\u003e\n                \u003ccomponents\u003e\n                    \u003ccomponent\u003eweaverbird-repo\u003c/component\u003e\n                \u003c/components\u003e\n                \u003cmodules\u003e\n                    \u003cmodule\u003e\n                        \u003cgroupId\u003ecom.myapp\u003c/groupId\u003e\n                        \u003cartifactId\u003emyapp-core\u003c/artifactId\u003e\n                        \u003cversion\u003e${myapp.version}\u003c/version\u003e\n                        \u003conModulePath\u003etrue\u003c/onModulePath\u003e\n                    \u003c/module\u003e\n                \u003c/modules\u003e\n            \u003c/configuration\u003e\n        \u003c/execution\u003e\n    \u003c/executions\u003e\n\u003c/plugin\u003e\n```\n\n## Requirements \u003ca name=\"requirements\"\u003e\u003c/a\u003e\n\nThe framework requires Java 25+ and JavaFX 25+ for GUI console.\n\n## Dependencies \u003ca name=\"dependencies\"\u003e\u003c/a\u003e\n\nThis project will be available on Maven Central in a few weeks.\n\nTo work with the framework core, the following dependency is required. It provides the key APIs and SPIs for building\nand managing modular components:\n\n```\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.techsenger.weaverbird\u003c/groupId\u003e\n    \u003cartifactId\u003eweaverbird-core\u003c/artifactId\u003e\n    \u003cversion\u003e${weaverbird.version}\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n## Code Building \u003ca name=\"code-building\"\u003e\u003c/a\u003e\n\nTo build the framework use standard Git and Maven commands:\n\n    git clone https://github.com/techsenger/weaverbird\n    cd weaverbird\n    mvn clean install\n\n## Running Demo \u003ca name=\"running-demo\"\u003e\u003c/a\u003e\n\nThe project contains various demo modules. Some of them can be run using the Exec Maven Plugin, some only via\n`.sh`/`.bat` scripts, and some support both options.\n\nInformation on how to run a specific `Demo.java` is provided in the Javadoc of that class.\n\n## License \u003ca name=\"license\"\u003e\u003c/a\u003e\n\nTechsenger Weaverbird is licensed under the Apache License, Version 2.0.\n\n## Contributing \u003ca name=\"contributing\"\u003e\u003c/a\u003e\n\nWe welcome all contributions. You can help by reporting bugs, suggesting improvements, or submitting pull requests\nwith fixes and new features. If you have any questions, feel free to reach out — we’ll be happy to assist you.\n\n## Support Us \u003ca name=\"support-us\"\u003e\u003c/a\u003e\n\nYou can support our open-source work through [GitHub Sponsors](https://github.com/sponsors/techsenger).\nYour contribution helps us maintain projects, develop new features, and provide ongoing improvements.\nMultiple sponsorship tiers are available, each offering different levels of recognition and benefits.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechsenger%2Fweaverbird","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftechsenger%2Fweaverbird","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftechsenger%2Fweaverbird/lists"}