JExten

JExten

JExten is a lightweight, annotation-driven extension framework for Java that leverages the Java Platform Module System (JPMS) to create dynamic, plugin-based applications.

Features

Quick Start

1. Add Dependencies

<dependency>
    <groupId>org.myjtools</groupId>
    <artifactId>jexten-core</artifactId>
    <version>1.0.0-alpha1</version>
</dependency>

<!-- For plugin management -->
<dependency>
    <groupId>org.myjtools</groupId>
    <artifactId>jexten-plugin-manager</artifactId>
    <version>1.0.0-alpha1</version>
</dependency>

2. Define an Extension Point

import org.myjtools.jexten.ExtensionPoint;

@ExtensionPoint(version = "1.0")
public interface Greeter {
    void greet(String name);
}

3. Implement an Extension

import org.myjtools.jexten.Extension;
import org.myjtools.jexten.Priority;
import org.myjtools.jexten.Scope;

@Extension
public class FriendlyGreeter implements Greeter {
    @Override
    public void greet(String name) {
        System.out.println("Hello, " + name + "!");
    }
}

4. Configure module-info.java

The JExten annotation processor will check the necessary configuration in your module-info.java file, and it will suggest you any missing uses or provides statements at compile time. Here is an example of how it should look:

module com.example.app {
    requires org.myjtools.jexten;

    exports com.example;

    uses com.example.Greeter;
    provides com.example.Greeter with com.example.FriendlyGreeter;
}

5. Discover and Use Extensions

import org.myjtools.jexten.ExtensionManager;

public class Main {
    public static void main(String[] args) {
        ExtensionManager manager = ExtensionManager.create();

        // Get the highest priority extension
        manager.getExtension(Greeter.class)
            .ifPresent(greeter -> greeter.greet("World"));

        // Get all extensions
        manager.getExtensions(Greeter.class)
            .forEach(greeter -> greeter.greet("Everyone"));
    }
}

Assembling Plugins

JExten supports dynamic plugin loading via PluginManager. A plugin is a collection of extensions packaged as a JPMS module in a ZIP bundle that contains all required artifacts along with the plugin.yaml definition file.

You can use the following configuration to automatically generate plugin bundles with Maven:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.13.0</version>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.myjtools.jexten</groupId>
                        <artifactId>jexten-processor</artifactId>
                        <version>1.0.0-alpha1</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.myjtools.jexten</groupId>
            <artifactId>jexten-maven-plugin</artifactId>
            <version>1.0.0-alpha1</version>
            <executions>
                <execution>
                    <goals>
                        <goal>generate-manifest</goal>
                        <goal>assemble-bundle</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <application>org.myjtools.jexten.example.app</application>
                <hostModule>org.myjtools.jexten.example.app</hostModule>
                <hostArtifact>org.myjtools.jexten.example:jexten-example-app</hostArtifact>
            </configuration>
        </plugin>
    </plugins>
</build>

Loading Plugins at Runtime

import org.myjtools.jexten.plugin.PluginManager;
import org.myjtools.jexten.ExtensionManager;

public class Application {
    public static void main(String[] args) throws IOException {
        Path pluginDir = Path.of("plugins");

        // Create plugin manager
        PluginManager pluginManager = new PluginManager(
            "org.myjtools.jexten.example.app",  // Application ID
            Application.class.getClassLoader(),
            pluginDir
        );

        // Install plugin from bundle
        pluginManager.installPluginFromBundle(
            pluginDir.resolve("my-plugin-1.0.0.zip")
        );

        // Create extension manager with plugin support
        ExtensionManager extensionManager = ExtensionManager.create(pluginManager);

        // Discover extensions from plugins
        extensionManager.getExtensions(Greeter.class)
            .forEach(g -> g.greet("Plugin User"));

        // Remove plugin at runtime
        pluginManager.removePlugin(new PluginID("com.example", "my-plugin"));
    }
}

Dependency Injection

JExten provides built-in dependency injection:

@Extension(extensionPoint = "com.example.Service")
public class MyService implements Service {

    @Inject
    private Repository repository;  // Inject another extension

    @Inject(value = "special")
    private Logger logger;          // Named injection

    @Inject
    private List<Plugin> plugins;   // Inject all implementations

    @PostConstruct
    void initialize() {
        // Called after all injections complete
    }
}

Plugin Manifest

Plugins are defined with a plugin.yaml manifest:

application: com.example.app
group: com.example
name: my-plugin
version: '1.0'
displayName: My Plugin
description: A sample plugin for the application
hostModule: com.example.my.plugin
url: https://github.com/example/my-plugin
licenseName: MIT
licenseText: MIT License

artifacts:
  com.example:
    - my-plugin-1.0.0
    - dependency-lib-2.3.1

extensions:
  com.example.Greeter:
    - com.example.plugin.CustomGreeter

extensionPoints:
  - com.example.plugin.NewExtensionPoint

# SHA-256 checksums for integrity verification (auto-generated by jexten-maven-plugin)
checksums:
  my-plugin-1.0.0.jar: a1b2c3d4e5f6...
  dependency-lib-2.3.1.jar: f6e5d4c3b2a1...

The checksums field contains SHA-256 hashes for each artifact in the bundle. These are automatically generated by jexten-maven-plugin during packaging and verified by PluginManager when loading plugins to ensure artifact integrity.

Plugin Integrity Verification

JExten includes built-in integrity verification for plugin artifacts using SHA-256 checksums.

How It Works

  1. During packaging: jexten-maven-plugin calculates SHA-256 checksums for all artifacts included in the bundle and embeds them in plugin.yaml

  2. During installation: When installPluginFromBundle() is called, checksums are verified immediately after extraction

  3. During discovery: When the application starts, installed plugins are verified against their stored checksums

Verification Failures

If an artifact's checksum doesn't match, PluginManager throws a PluginException:

PluginException: Checksum verification failed for plugin com.example:my-plugin :
artifact my-plugin-1.0.0.jar has been modified or corrupted
(expected a1b2c3..., found d4e5f6...); please reinstall the plugin

Backwards Compatibility

Plugins without checksums (created with older versions of jexten-maven-plugin) continue to work normally - integrity verification is skipped when no checksums are present.

Architecture

┌─────────────────────────────────────────────────────────────┐
│                      Host Application                       │
│  ┌─────────────────────────────────────────────────────────┐│
│  │              ExtensionManager                           ││
│  │   ┌─────────────┐  ┌─────────────┐  ┌───────────┐       ││
│  │   │ServiceLoader│  │InjectionHdlr│  │   Cache   │       ││
│  │   └─────────────┘  └─────────────┘  └───────────┘       ││
│  └─────────────────────────────────────────────────────────┘│
│                            │                                │
│  ┌─────────────────────────────────────────────────────────┐│
│  │              PluginManager                              ││
│  │   ┌─────────────┐  ┌─────────────┐  ┌───────────┐       ││
│  │   │ ModuleLayer │  │  Manifest   │  │ Artifacts │       ││
│  │   │    Tree     │  │   Parser    │  │   Store   │       ││
│  │   └─────────────┘  └─────────────┘  └───────────┘       ││
│  └─────────────────────────────────────────────────────────┘│
│                            │                                │
├────────────────────────────┼────────────────────────────────┤
│          Boot Layer        │         Plugin Layers          │
│  ┌───────────────────┐     │    ┌───────────────────┐       │
│  │   Host Module     │     │    │    Plugin A       │       │
│  │  @ExtensionPoint  │◄────┼────│   @Extension      │       │
│  └───────────────────┘     │    └───────────────────┘       │
│                            │    ┌───────────────────┐       │
│                            │    │    Plugin B       │       │
│                            │    │   @Extension      │       │
│                            │    └───────────────────┘       │
└─────────────────────────────────────────────────────────────┘

Comparison with Other Frameworks

JExten vs OSGi vs Layrry

FeatureJExtenOSGiLayrry
FoundationJPMS (Java 21+)Custom classloadersJPMS
ConfigurationAnnotations + YAMLManifest headersYAML/TOML/API
Learning CurveLowHighMedium
Runtime WeightLightweightHeavyLightweight
VersioningSemantic (built-in)Full version rangesMaven coordinates
Dynamic LoadingYesYesYes
Dependency InjectionBuilt-inDeclarative ServicesNo
Service ModelServiceLoader + ExtensionsOSGi ServicesServiceLoader
Build IntegrationMaven/Gradle pluginsBND toolsMaven/Gradle
MaturityNew20+ years2020+

When to Choose JExten

Choose JExten when you need:

Choose OSGi when you need:

Choose Layrry when you need:

Detailed Comparison

OSGi

OSGi is the veteran of Java modularity, predating JPMS by nearly two decades. It provides comprehensive modularity features including per-bundle classloaders, dynamic services, and sophisticated version ranges.

Advantages over JExten:

Disadvantages vs JExten:

Layrry

Layrry is a modern launcher for layered Java applications, created by Gunnar Morling. It provides YAML/TOML-based configuration for module layer hierarchies.

Advantages over JExten:

Disadvantages vs JExten:

Migration Paths

From OSGi to JExten

  1. Replace @Component with @Extension
  2. Replace OSGi service interfaces with @ExtensionPoint
  3. Convert MANIFEST.MF headers to module-info.java
  4. Replace @Reference with @Inject
  5. Update build from BND to standard Maven/Gradle

From Layrry to JExten

  1. Add @ExtensionPoint annotations to service interfaces
  2. Add @Extension annotations to implementations
  3. Convert layers.yaml to plugin.yaml manifests
  4. Use PluginManager instead of Layrry launcher

Modules

ModuleDescription
jexten-coreCore annotations and ExtensionManager API
jexten-plugin-managerPlugin management, dynamic loading, and integrity verification
jexten-processorCompile-time annotation processor
jexten-maven-pluginMaven plugin for bundling plugins with checksum generation
jexten-maven-artifact-storeMaven repository integration

Building from Source

# Clone the repository
git clone https://github.com/org-myjtools/jexten.git
cd jexten

# Build with Maven
mvn clean install

# Run tests
mvn test

Requirements

Examples

See the examples/ directory for complete working examples:

License

MIT License

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting pull requests.

Resources