What is Context-Aware Configuration?

Context-aware configurations are related to a content resource or a resource tree (e.g. a web site or tenant site). An application may need different configuration for different sites, regions, and tenants = different contexts.

Inheritance for nested contexts and from global fallback values is supported.

Why Not OSGi Configuration?

  • OSGi configuration is targeted to system-wide configuration (singleton services, factory = global list)
  • It's difficult to store context-aware configuration in OSGi config

Recommendation: Use Sling Context-Aware Configuration for config related to content paths. Use OSGi configuration for everything else.

Java API Overview

Two levels:

  • ConfigurationResourceResolver (low-level) — access configuration resources (e.g. workflow definitions). Methods: getResource/getResourceCollection for named config resources by context, getContextPath/getAllContextPaths.
  • ConfigurationBuilder (high-level) — access configuration data (key/value pairs). Get via adapting context resource or via ConfigurationResolver OSGi service.

In most cases you only use the high-level API.

Configuration as ValueMap

// Singleton named configuration as ValueMap
ValueMap props = resource.adaptTo(ConfigurationBuilder.class)
    .name("simple-config").asValueMap();
String param1 = props.get("stringParam", String.class);
int param2 = props.get("intParam", 5);

// Collection of configurations as ValueMaps
Collection<ValueMap> propsList = resource
    .adaptTo(ConfigurationBuilder.class)
    .name("simple-config").asValueMapCollection();

Typed Configuration Class — Singleton

@Configuration
public @interface MatrixConfig {
    String stringParam();
    int intParam() default 5;
    boolean boolParam();
}

// Access typed singleton configuration
MatrixConfig config = resource.adaptTo(ConfigurationBuilder.class)
    .as(MatrixConfig.class);
String param1 = config.stringParam();
int param2 = config.intParam();

// Configuration name is derived from annotation class name

Typed Configuration Class — Collection

@Configuration(collection = true)
public @interface SentinelListConfig {
    String stringParam();
    int intParam() default 5;
    boolean boolParam();
}

// Access typed configuration collection
Collection<SentinelListConfig> configs = resource
    .adaptTo(ConfigurationBuilder.class)
    .asCollection(SentinelListConfig.class);
for (SentinelListConfig item : configs) {
    // ...
}

Nested Configurations

@Configuration
public @interface ArchitectConfig {
    String sampleParam();
    MatrixConfig subConfig();
    SentinelListConfig[] subListConfig();
}

// Access nested configuration
ArchitectConfig config = resource.adaptTo(ConfigurationBuilder.class)
    .as(ArchitectConfig.class);
String param1 = config.subConfig().stringParam();
int param2 = config.subConfig().intParam();
for (SentinelListConfig item : config.subListConfig()) {
    // ...
}

Context-Aware Configuration in HTL

<!-- Accessing singleton configuration -->
<dl>
  <dt>stringParam:</dt>
  <dd>${caconfig['org.matrix.MatrixConfig'].stringParam}</dd>
</dl>

<!-- Accessing configuration collection -->
<ul data-sly-list.item="${caconfig['org.matrix.SentinelListConfig']}">
  <li>stringParam: ${item.stringParam}</li>
</ul>

<!-- Nested config: use slash as separator -->
${caconfig['org.matrix.ArchitectConfig']['nestedConfig/stringParam']}

Annotation Classes and bnd Plugin

Configuration classes are Java annotation classes (never used as annotations — the syntax is reused for configuration metadata):

  • Configuration name (defaults to class name)
  • Parameters with names and types
  • Default values
  • Nested configuration structures

All configuration classes must be registered via bundle header Sling-ContextAware-Configuration-Classes. A bnd plugin sets this automatically:

<_plugin>org.apache.sling.caconfig.bndplugin.ConfigurationClassScannerPlugin</_plugin>

Default Content Model

Default implementation stores all configuration below /conf. Fallback paths: /conf/global, /apps/conf, /libs/conf.

Contexts are defined by setting sling:configRef property on root content resources, pointing to the associated /conf path.

/content/mysite
  @sling:configRef = "/conf/mysite"

/conf/mysite/sling:configs/org.matrix.MatrixConfig
  @stringParam = "value1"
  @intParam = 123

Persistence Structure

Singleton: /conf/{site}/sling:configs/{ConfigName}/@properties

Collection: /conf/{site}/sling:configs/{ConfigName}/item1/@properties, .../item2/@properties

Nested: /conf/{site}/sling:configs/{ConfigName}/@sampleParam + /subConfig/@properties + /subListConfig/item1/@properties

Resource and Property Inheritance

Resource inheritance (collections): Not enabled by default. Enable by setting sling:configCollectionInherit=true — produces a merged list with unique collection item names from the lookup chain.

Property inheritance: Not enabled by default. Enable by setting sling:configPropertyInherit=true — properties not defined on current resource are inherited from the next matching resource in lookup order.

By setting these properties on multiple resources in the lookup order, deeper inheritance chains are formed.

SPI — Service Provider Interfaces

A set of SPIs allows overlaying, enhancing, or replacing the default implementation:

  • ContextPathStrategy — how context paths and config references are detected
  • ConfigurationResourceResolvingStrategy — where config data is looked up, how inheritance works
  • ConfigurationInheritanceStrategy — if/how resources are inherited across the chain
  • ConfigurationPersistenceStrategy2 — persistence structure of config within resources
  • ConfigurationMetadataProvider — provide metadata about configurations
  • ConfigurationOverrideProvider — provide custom overrides

Multiple strategies supported simultaneously — iterated in service ranking order. First implementation that "feels responsible" wins.

Override Providers

Override context-aware config values globally or for specific content paths:

Syntax:

{configName}/{propertyName}={propertyJsonValue}
[{contextPath}]{configName}/{propertyName}={propertyJsonValue}
{configName}={propertyJsonObject}
[{contextPath}]{configName}={propertyJsonObject}

Built-in providers:

  • System properties (prefixed sling.caconfig.override.)
  • OSGi factory configuration ("Apache Sling Context-Aware OSGi Configuration Override Provider")

Unit Test Support — caconfig-mock-plugin

import static org.apache.sling.testing.mock.caconfig.ContextPlugins.CACONFIG;

public class MatrixConfigTest {

    @Rule
    public SlingContext context = new SlingContextBuilder()
        .plugin(CACONFIG)
        .build();

    @Before
    public void setUp() {
        // register configuration annotation class
        MockContextAwareConfig.registerAnnotationClasses(
            context, MatrixConfig.class);

        // write config values required for tests
        MockContextAwareConfig.writeConfiguration(context,
            "/content/region1", MatrixConfig.class,
            "stringParam", "value1",
            "intParam", 123);
    }
}