Skip to main content

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 Connector Development

Related Documentation
This guide covers developing OSGI plugin connectors, which are modular, hot-deployable connector extensions for Hub.

Table of Contents


Plugin vs Embedded Connectors

Comparison Table

FeatureEmbedded ConnectorsPlugin Connectors
DeploymentBuilt into application JARSeparate OSGI bundles
Hot DeployRequires restartRuntime install/uninstall
InterfaceCustomConnectorplugin.sdk.Connector
ValidationRich PayloadSpecManual input processing
Error HandlingFull exception hierarchyBasic error handling
Spring IntegrationFull framework supportLimited Spring features
TestingMockConnector supportManual mocking
ConfigurationSpring configurationOSGI service properties
PackagingPart of main applicationIndependent JAR/bundle

When to Use Plugin Connectors

Use Plugin Connectors for:
  • Third-party integrations
  • Customer-specific connectors
  • Experimental connectors
  • Connectors with different release cycles
  • Connectors requiring hot deployment
  • Modular architecture requirements
Use Embedded Connectors for:
  • Core application functionality
  • Stable, well-tested connectors
  • Connectors requiring complex Spring integration
  • Performance-critical connectors
  • Connectors with rich validation needs

Development Setup

Project Structure

my-plugin/
├── src/main/java/
│   └── com/company/plugin/
│       ├── MyConnector.groovy
│       └── DSLComponent.groovy
└── build.gradle

Dependencies

build.gradle
plugins {
    id 'groovy'
    id 'biz.aQute.bnd.builder' version '7.1.0'
}

dependencies {
    // Hub Plugin SDK
    implementation 'com.zenoo.hub:hub-plugin-sdk:1.0.0'

    // Testing
    testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0'
    testImplementation 'org.mockito:mockito-core:4.8.0'
}

// OSGI Bundle Configuration
jar {
    manifest {
        attributes(
                'Bundle-SymbolicName': 'my-plugin',
                'Bundle-Version': '0.1.0',
                'Service-Component': '*'
        )
    }
}

Plugin Implementation

Basic Plugin Connector

package com.company.plugin

import com.zenoo.hub.dsl.attribute.Attribute
import com.zenoo.hub.dsl.attribute.AttributeBuilder
import com.zenoo.hub.plugin.sdk.Connector
import groovy.util.logging.Slf4j
import org.osgi.service.component.annotations.Component
import reactor.core.publisher.Mono

@Slf4j
@Component(immediate = true)
class MyConnector implements Connector {

    @Override
    String name() {
        return 'my-plugin'
    }

    @Override
    Mono<Attribute> exchange(Attribute input) {
        log.info("Processing input: {}", input)
         // Return result
         return Mono.just(AttributeBuilder.of([
             processed: input,
             timestamp: System.currentTimeMillis(),
             success: true,
             plugin: name()
         ]))
    }

}

Plugin DSL Component

Plugins can also define DSL components that provide reusable workflows, functions, and exposed endpoints. This is done by implementing the ComponentFactory interface:
package com.company.plugin

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

@Component
class MyPluginDSLComponent implements ComponentFactory {

    @Override
    ComponentBuilder define() {
        return ComponentBuilder.component {
            dependencies {
                connector 'my-plugin'
            }

            // Exposed function accessible via Client API
            exposed('process-data') {
                payload ->
                    function('validate-input') {
                        input payload
                        namespace validated
                    }

                    exchange('process') {
                        connector 'my-plugin'
                        config validated
                    }.orError {
                       error([error: 'Processing failed', timestamp: System.currentTimeMillis()])
                    }
            }

            // Reusable workflow
            workflow('document-processing') {
                route('upload') {
                    uri '/plugin/upload'
                    validate {
                        required 'document'
                        optional 'options'
                    }
                }

                function('process-document') {
                    input payload
                    namespace result
                }

                route('results') {
                    uri '/plugin/results'
                    export data: result
                    terminal()
                }
            }

            // Reusable function
            function('validate-input') {
                input ->
                    mapper('input-mapper') {
                        payload -> [
                            data: payload.data,
                            isValid: payload.data != null,
                            timestamp: System.currentTimeMillis()
                        ]
                    }
            }

            // Data mapper
            mapper('input-mapper') {
                payload -> [
                    data: payload.data,
                    isValid: payload.data != null,
                    timestamp: System.currentTimeMillis()
                ]
            }
        }
    }
}