Configuration
ConfigurationAdmin is a built-in Pandino service for managing runtime configuration. It lets you create, update, and delete configuration objects identified by a persistent identifier (PID). Services that implement ManagedService receive configuration updates automatically.
Core Concepts
- A Configuration is a property map identified by a PID string.
- ConfigurationAdmin is the service used to create and retrieve configurations.
- ManagedService is the interface services implement to receive configuration.
- ManagedServiceFactory creates multiple service instances, one per factory configuration.
Getting a Configuration
Obtain the ConfigurationAdmin service from the registry, then use getConfiguration() to create or retrieve a configuration by PID:
import type { BundleActivator, BundleContext } from '@pandino/pandino';
import type { ConfigurationAdmin } from '@pandino/pandino';
export default class Activator implements BundleActivator {
async start(context: BundleContext): Promise<void> {
const ref = context.getServiceReference<ConfigurationAdmin>('ConfigurationAdmin');
const configAdmin = context.getService(ref!);
// Get or create a configuration
const config = await configAdmin!.getConfiguration('com.example.database');
// Read current properties (null if never updated)
console.log(config.getProperties());
console.log(config.getPid()); // 'com.example.database'
}
}getConfiguration() creates the configuration if it does not already exist. It does not deliver the configuration to any service until you call update().
Updating a Configuration
Call config.update(properties) to set the configuration properties. This immediately delivers the properties to any ManagedService registered with a matching service.pid:
const config = await configAdmin.getConfiguration('com.example.database');
await config.update({
connectionString: 'postgresql://localhost:5432/mydb',
maxPoolSize: 10,
timeout: 30000,
});ManagedService
A ManagedService receives configuration updates through its updated() callback. Register it with a service.pid property that matches the configuration PID:
import type {
BundleActivator,
BundleContext,
ServiceRegistration,
} from '@pandino/pandino';
import type { ManagedService } from '@pandino/pandino';
class DatabaseService implements ManagedService {
private connectionString = '';
private maxPoolSize = 5;
async updated(properties: Record<string, any> | null): Promise<void> {
if (properties === null) {
// Configuration has been deleted; reset to defaults
this.connectionString = '';
this.maxPoolSize = 5;
return;
}
this.connectionString = properties['connectionString'];
this.maxPoolSize = properties['maxPoolSize'] || 5;
console.log(`Database configured: ${this.connectionString}`);
}
}
export default class Activator implements BundleActivator {
private registration?: ServiceRegistration<ManagedService>;
async start(context: BundleContext): Promise<void> {
this.registration = context.registerService(
'ManagedService',
new DatabaseService(),
{ 'service.pid': 'com.example.database' },
);
}
async stop(context: BundleContext): Promise<void> {
this.registration?.unregister();
}
}When updated() is called:
- With a properties object -- A configuration exists and has been created or changed.
- With
null-- The configuration has been deleted. The service should revert to defaults.
Deleting a Configuration
Call config.delete() to remove a configuration. This triggers updated(null) on the matching ManagedService:
const config = await configAdmin.getConfiguration('com.example.database');
await config.delete();Factory Configurations
Factory configurations create multiple instances of a service, each with its own PID and properties. This is useful when you need several instances of the same service type with different settings (e.g., multiple database connections).
Use createFactoryConfiguration() to create a new configuration under a factory PID:
const config1 = await configAdmin.createFactoryConfiguration('com.example.datasource');
await config1.update({ name: 'primary', url: 'postgresql://primary:5432/db' });
const config2 = await configAdmin.createFactoryConfiguration('com.example.datasource');
await config2.update({ name: 'readonly', url: 'postgresql://replica:5432/db' });Each call to createFactoryConfiguration() generates a unique PID. You can check the factory PID with config.getFactoryPid().
ManagedServiceFactory
A ManagedServiceFactory handles multiple factory-created instances. It receives creation, update, and deletion callbacks for each configuration:
import type { ManagedServiceFactory } from '@pandino/pandino';
class DataSourceFactory implements ManagedServiceFactory {
private dataSources = new Map<string, DataSource>();
getName(): string {
return 'DataSource Factory';
}
async updated(pid: string, properties: Record<string, any>): Promise<void> {
// Create or update a data source instance
const existing = this.dataSources.get(pid);
if (existing) {
existing.reconfigure(properties);
console.log(`Updated data source: ${properties['name']}`);
} else {
const ds = new DataSource(properties['url'], properties['name']);
this.dataSources.set(pid, ds);
console.log(`Created data source: ${properties['name']}`);
}
}
async deleted(pid: string): Promise<void> {
const ds = this.dataSources.get(pid);
ds?.close();
this.dataSources.delete(pid);
console.log(`Deleted data source: ${pid}`);
}
}Register the factory with a service.pid matching the factory PID:
context.registerService(
'ManagedServiceFactory',
new DataSourceFactory(),
{ 'service.pid': 'com.example.datasource' },
);Integration with Declarative Services
SCR components can receive configuration automatically through the configurationPid and configurationPolicy options. This eliminates the need to implement ManagedService manually.
import { Component, Service, Activate, Modified } from '@pandino/decorators';
import type { ComponentContext } from '@pandino/pandino';
@Component({
name: 'com.example.cache-service',
configurationPid: 'com.example.cache',
configurationPolicy: 'optional',
immediate: true,
})
@Service({ interfaces: ['CacheService'] })
class CacheServiceComponent implements CacheService {
private maxEntries = 100;
private ttlMs = 60000;
@Activate
activate(context: ComponentContext): void {
const props = context.getProperties();
this.maxEntries = props['maxEntries'] || 100;
this.ttlMs = props['ttlMs'] || 60000;
console.log(`Cache configured: max=${this.maxEntries}, ttl=${this.ttlMs}ms`);
}
@Modified
modified(context: ComponentContext): void {
// Called when the configuration is updated at runtime
const props = context.getProperties();
this.maxEntries = props['maxEntries'] || 100;
this.ttlMs = props['ttlMs'] || 60000;
console.log(`Cache reconfigured: max=${this.maxEntries}, ttl=${this.ttlMs}ms`);
}
// ... cache implementation ...
}Configuration policies control when the component activates:
| Policy | Behavior |
|---|---|
'optional' | Activates with or without configuration. Merges properties if configuration exists. |
'require' | Blocks activation until a matching configuration is available. |
'ignore' | Ignores configuration entirely, even if one exists for the PID. |
To push configuration to a declarative service component, use ConfigurationAdmin as usual:
const config = await configAdmin.getConfiguration('com.example.cache');
await config.update({ maxEntries: 500, ttlMs: 120000 });
// The CacheServiceComponent's @Modified method is called automaticallyListing Configurations
Use listConfigurations() with an optional LDAP filter to find existing configurations:
// List all configurations
const allConfigs = await configAdmin.listConfigurations();
// List configurations matching a filter
const dbConfigs = await configAdmin.listConfigurations('(service.factoryPid=com.example.datasource)');ConfigurationListener
To be notified when any configuration changes across the framework, register a ConfigurationListener:
import type { ConfigurationListener, ConfigurationEvent } from '@pandino/pandino';
class ConfigAuditLogger implements ConfigurationListener {
configurationEvent(event: ConfigurationEvent): void {
const pid = event.getPid();
const type = event.getType(); // 1 = UPDATED, 2 = DELETED
if (type === 1) {
console.log(`Configuration updated: ${pid}`);
} else if (type === 2) {
console.log(`Configuration deleted: ${pid}`);
}
}
}
context.registerService('ConfigurationListener', new ConfigAuditLogger());Next Steps
- Core Framework API -- Full API reference for ConfigurationAdmin, Configuration, and ManagedService
- Declarative Services -- How SCR components integrate with configuration