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
| Feature | Embedded Connectors | Plugin Connectors |
|---|
| Deployment | Built into application JAR | Separate OSGI bundles |
| Hot Deploy | Requires restart | Runtime install/uninstall |
| Interface | CustomConnector | plugin.sdk.Connector |
| Validation | Rich PayloadSpec | Manual input processing |
| Error Handling | Full exception hierarchy | Basic error handling |
| Spring Integration | Full framework support | Limited Spring features |
| Testing | MockConnector support | Manual mocking |
| Configuration | Spring configuration | OSGI service properties |
| Packaging | Part of main application | Independent 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()
]
}
}
}
}