> ## Documentation Index
> Fetch the complete documentation index at: https://platform.docs.zenoo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin System in Zenoo Hub

> Comprehensive guide to the plugin system for deploying custom connectors and components in the Zenoo Hub.

# Plugin System in Zenoo Hub

The Zenoo Hub features a robust plugin system that empowers developers to extend the platform with custom connectors and components, enabling seamless integration with third-party services and rapid adaptation to evolving business needs.

## Overview

The plugin system allows you to package, deploy, and manage custom logic independently of the Hub core. Plugins can introduce new connectors, orchestration steps, or business logic, all while maintaining a clean separation from the main codebase.

## Benefits

* **Extensibility:** Add new features and integrations without modifying the Hub core.
* **Modularity:** Develop, test, and deploy plugins independently.
* **Maintainability:** Isolate customer-specific or experimental logic from the main platform.
* **Rapid Innovation:** Prototype and roll out new capabilities quickly.

## Architecture

Plugins are loaded at runtime by the Hub, using a dedicated classloader to ensure isolation and prevent conflicts. Each plugin can declare its own dependencies, resources, and configuration.

### Types of Plugins

* **Custom Connectors:** Integrate with external APIs, databases, or services.
* **Transport Customizers:** Enhance HTTP transports with custom authentication, filters, circuit breakers, or advanced features.
* **Hub Components:** Add new orchestration steps, validators, or business logic.

## Development Workflow

1. **Create a Plugin Project:** Use the provided SDK or follow the [Plugin Connector Development](/technical-specification/hub-backend/connectors/plugin-development) guide.
2. **Implement Plugin Logic:** Write your custom connector or component, specifying dependencies as needed.
3. **Package the Plugin:** Build a JAR (or other supported format) containing your code and resources.
4. **Test Locally:** Use the Hub's testing tools or a local instance to validate your plugin.

## Example: Adding a New Hub Component as a Plugin

Let's walk through creating a custom Hub component as a plugin using `com.zenoo.hub.plugin.sdk.ComponentFactory`.

### 1. Implement the Plugin Component

Create a Groovy class that implements `ComponentFactory` and uses the `ComponentBuilder` DSL to define your component logic:

```groovy theme={null}
package com.zenoo.plugin

import com.zenoo.hub.plugin.sdk.ComponentBuilder
import com.zenoo.hub.plugin.sdk.ComponentFactory
import org.osgi.service.component.annotations.Component

@Component
class PluginComponent implements ComponentFactory {
    @Override
    ComponentBuilder define() {
        return ComponentBuilder.component {
            dependencies {
                connector 'plugin'
            }
            exposed('test') { payload ->
                exchange('plugin') {
                    connector 'plugin'
                    config payload
                }
            }
        }
    }
}
```

* The `@Component` annotation registers this class as an OSGi component.
* The `define()` method returns a `ComponentBuilder` using a Groovy DSL to declare dependencies and exposed operations.
* The `exposed('test')` block defines an entry point named `test` for this component.

### 2. Package the Plugin

* Ensure your build includes the necessary dependencies and OSGi metadata.
* Build your project as a JAR file (a fat/uber JAR if needed).

### 3. Deploy the Plugin

* Place the JAR in the Hub's plugin directory or deploy it using the Hub's deployment mechanism.
* The Hub will detect and load your new component, making it available in workflow definitions as `test` (or the name you expose).

You can now use your custom component in Hub workflows just like any built-in component.

## Example: Creating a Transport Customizer Plugin

Transport customizers allow you to enhance HTTP transports defined in DSL with advanced features while preserving the base configuration (baseUrl, authentication). This follows the **decorator pattern** where customizers add functionality without replacing the underlying transport.

### Use Cases for Transport Customizers

* **Advanced Authentication:** OAuth2 flows, mTLS, API key rotation
* **Resilience Patterns:** Circuit breakers, retry logic, fallback mechanisms
* **Custom Headers:** Dynamic headers, correlation IDs, API versioning
* **Monitoring:** Request/response logging, timing metrics, tracing
* **Content Transformation:** Custom codecs, request/response interceptors

### 1. Create a Typed Configuration Class

Define a configuration class implementing `PluginConfig` with JSR-303 validation:

```java theme={null}
package com.zenoo.plugin;

import com.zenoo.hub.plugin.sdk.PluginConfig;
import jakarta.validation.constraints.NotNull;
import lombok.Data;

@Data
public class HttpTransportConfig implements PluginConfig {

    @NotNull(message = "Custom header name is required")
    private String customHeaderName = "X-Custom-Header";

    @NotNull(message = "Custom header value is required")
    private String customHeaderValue = "default-value";

    private Boolean enableDetailedLogging = false;
}
```

**Key Points:**

* Use `@Data` from Lombok for automatic getters/setters
* Add JSR-303 validation annotations (`@NotNull`, `@Min`, `@Max`, etc.)
* Provide sensible defaults for optional fields
* No need for Jackson annotations (Hub handles mapping automatically)

### 2. Implement the Transport Customizer

Create a class implementing `TransportCustomizer` and register it as an OSGi component:

```java theme={null}
package com.zenoo.plugin;

import com.zenoo.hub.plugin.sdk.TransportCustomizer;
import com.zenoo.hub.plugin.sdk.PluginConfig;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.UUID;

@Component
public class CustomHttpTransportCustomizer implements TransportCustomizer {

    private static final Logger log = LoggerFactory.getLogger(CustomHttpTransportCustomizer.class);

    @Override
    public String name() {
        return "custom-http";
    }

    @Override
    public Class<? extends PluginConfig> configType() {
        return HttpTransportConfig.class;
    }

    @Override
    public WebClient customize(WebClient baseClient, PluginConfig config) {
        if (config == null) {
            log.debug("No configuration provided, returning base client unchanged");
            return baseClient;
        }

        HttpTransportConfig httpConfig = (HttpTransportConfig) config;
        log.info("Customizing HTTP transport with config: {}", httpConfig);

        // Use mutate() to ENHANCE base client (preserves DSL baseUrl & auth)
        WebClient.Builder builder = baseClient.mutate();

        // Add custom headers filter
        ExchangeFilterFunction customHeadersFilter = ExchangeFilterFunction.ofRequestProcessor(request -> {
            ClientRequest modifiedRequest = ClientRequest.from(request)
                    .header(httpConfig.getCustomHeaderName(), httpConfig.getCustomHeaderValue())
                    .header("X-API-Version", "1.0")
                    .header("X-Correlation-ID", UUID.randomUUID().toString())
                    .build();
            return Mono.just(modifiedRequest);
        });
        builder.filter(customHeadersFilter);

        // Add timing filter
        ExchangeFilterFunction timingFilter = (request, next) -> {
            long startTime = System.currentTimeMillis();
            return next.exchange(request).doOnSuccess(response -> {
                long duration = System.currentTimeMillis() - startTime;
                log.info("Request to {} took {}ms", request.url(), duration);
            });
        };
        builder.filter(timingFilter);

        // Conditional logging based on config
        if (Boolean.TRUE.equals(httpConfig.getEnableDetailedLogging())) {
            builder.filter(ExchangeFilterFunction.ofRequestProcessor(request -> {
                log.debug("Outgoing request: {} {}", request.method(), request.url());
                return Mono.just(request);
            }));
        }

        return builder.build();
    }
}
```

**Key Points:**

* **`name()`**: Returns the unique identifier for this customizer within the plugin
* **`configType()`**: Returns the configuration class type for automatic mapping and validation
* **`customize()`**: Receives the pre-configured `baseClient` (with DSL baseUrl & auth) and your typed config
* **Always use `baseClient.mutate()`** to preserve DSL settings while adding enhancements
* Return the unmodified `baseClient` if no customization is needed

### 3. Using Transport Customizers in DSL

Define the transport in your DSL with base configuration, then reference the customizer:

```groovy theme={null}
// Define DSL transport with baseUrl and authentication
transport('custom-http') {
    baseUrl 'https://api.example.com'
    bearer {
        clientId 'my-client'
        clientSecret 'secret'
        tokenUri 'https://auth.example.com/token'
    }
}

// Component configuration for the customizer
config {
    customHeaderName 'X-API-Key'
    customHeaderValue config('api.key')
    enableDetailedLogging true
}

// Use in workflow - gets DSL baseUrl + auth + custom enhancements
workflow('my-workflow') {
    exchange('api-call') {
        http {
            transport 'custom-http'  // References customizer by name
            url '/api/endpoint'
            method 'POST'
            body([data: 'value'])
        }
    }
}
```

**How It Works:**

1. Hub creates base `WebClient` from DSL transport (baseUrl + bearer auth)
2. Hub looks up customizer by name `'custom-http'`
3. Hub maps component `config` to `HttpTransportConfig` and validates it
4. Hub calls `customizer.customize(baseClient, typedConfig)`
5. Your customizer adds filters using `mutate()`
6. Enhanced `WebClient` is used for HTTP requests

### 4. Build Configuration for OSGi

Configure your plugin's `build.gradle` to exclude framework packages from OSGi imports:

```gradle theme={null}
plugins {
    id 'java-library'
    id 'biz.aQute.bnd.builder' version '7.1.0'
}

dependencies {
    implementation project(':component-sdk')
    annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
}

jar {
    manifest {
        attributes(
            'Bundle-SymbolicName': 'my-plugin',
            'Bundle-Version': '1.0.0',
            'Service-Component': '*',
            'Import-Package': '!groovy.*,!org.springframework.*,!reactor.*,*'
        )
    }
}
```

**Key Points:**

* Exclude Spring and Reactor from Import-Package (they're loaded via boot delegation)
* `'Service-Component': '*'` enables OSGi Declarative Services
* The `bnd.builder` plugin generates proper OSGi metadata

### 5. Karaf Configuration

Ensure your Karaf `config.properties` includes boot delegation for framework classes:

```properties theme={null}
org.osgi.framework.bootdelegation=org.codehaus.groovy.*,groovy.*,org.springframework.*,reactor.*
```

This allows plugins to use Spring and Reactor classes from the Hub's classpath without explicit OSGi wiring.

## Deployment Process

* **Deploy to Hub:** Place the plugin artifact in the designated plugins directory or use the Hub's deployment API.
* **Activation:** The Hub detects new plugins and loads them at runtime, making new components available for use in workflows.
* **Hot Reload (if supported):** Some environments allow plugins to be updated without restarting the Hub.

## Versioning and Isolation

* Each plugin is versioned independently, allowing safe upgrades and rollbacks.
* Plugins run in isolated classloaders to avoid dependency conflicts.

## Best Practices

### General Plugin Development

* Keep plugins focused and single-purpose.
* Document plugin APIs and configuration options.
* Use semantic versioning for plugin releases.
* Test plugins thoroughly before deployment.
* Monitor plugin performance and errors using Hub's observability tools.

### Transport Customizer Best Practices

* **Always use `baseClient.mutate()`** to preserve DSL configuration (baseUrl, auth)
* **Return unmodified baseClient** when config is null or no customization needed
* **Use typed configuration** with `PluginConfig` and JSR-303 validation for type safety
* **Avoid overriding DSL settings** unless explicitly required by your use case
* **Be cautious with logging** - avoid exposing sensitive data (credentials, tokens, PII)
* **Implement proper error handling** with meaningful exceptions
* **Consider thread safety** - the returned WebClient is cached and used concurrently
* **Add filters for cross-cutting concerns** - logging, timing, custom headers
* **Test with various DSL transport configurations** to ensure decorator pattern works correctly
* **Document which DSL settings your customizer depends on** or modifies

## Related Guides

* [Plugin Connector Development](/technical-specification/hub-backend/connectors/plugin-development)
* [Custom Connector Development](/technical-specification/hub-backend/connectors/custom-development)
