Fork me on GitHub

ConfigServiceRepository

ConfigServiceRepository leverages brXM's production ConfigurationConfigService to create HST configuration in tests, providing production-identical JCR structure without manual node construction.

Key Benefits:

  • Production Parity: Uses exact same bootstrap code as real brXM
  • Zero Maintenance: brXM structure changes propagate automatically
  • Explicit Control: Loads only your test HCM modules (no framework dependencies)
  • Addon Support: Dependency HCM modules auto-discovered from classpath (zero config)
  • Proven: Works with both JAX-RS and PageModel tests

Annotation-Based Usage (Recommended)

With BRUT 5.1.0+, ConfigServiceRepository integration is a one-liner via the loadProjectContent attribute:


@BrxmJaxrsTest(
    beanPackages = {"org.example.model"},
    resources = {MyResource.class},
    loadProjectContent = true  // Enables ConfigServiceRepository
)
class MyTest {

    @Test
    void testEndpoint(DynamicJaxrsTest brxm) {
        brxm.request()
            .get("/site/api/my-endpoint")
            .assertStatus(200);
    }
}
        

What loadProjectContent = true does:

  • Auto-detects your project namespace from pom.xml groupId
  • Loads HCM modules from target/test-classes/META-INF/hcm-module.yaml
  • Creates production-identical HST configuration
  • All without manual Spring XML configuration

You still need the HCM module descriptor and config files (see Manual Setup below), but the annotation handles the repository wiring automatically.

Addon HCM Modules (Zero Config)

BRUT automatically discovers addon HCM modules from the test classpath. Any JAR that provides META-INF/hcm-module.yaml and is not a Hippo platform module (group prefix hippo / onehippo) and not your project's own module is loaded automatically — before your project modules so its node types are registered first.

Default: Zero Config

No annotation parameter is needed — addon modules are discovered and loaded automatically:


// brxm-discovery-cms (or any other addon JAR) is auto-discovered from the classpath
@BrxmPageModelTest
class MyPageModelTest {
}
          

Opt Out of a Specific Module

Use excludeDependencyHcmModules to skip a module that causes conflicts:


@BrxmPageModelTest(excludeDependencyHcmModules = {"some-conflicting-module"})
class MyPageModelTest {
}
          

Force-Include a Module

Use dependencyHcmModules to explicitly include a module that auto-discovery would miss (e.g., its group name matches a platform prefix):


@BrxmPageModelTest(dependencyHcmModules = {"some-special-module"})
class MyPageModelTest {
}
          

Discovery Rules

A classpath JAR is treated as an addon and loaded automatically when all of the following are true:

  • It contains META-INF/hcm-module.yaml
  • Its HCM group name does not start with hippo or onehippo (platform modules)
  • Its HCM group name does not match your project's group (project modules are loaded separately)
  • It is not listed in excludeDependencyHcmModules

Manual Setup (Legacy)

For legacy abstract class tests or advanced customization, use manual Spring XML configuration:

1. Add HCM Module Descriptor

File: src/test/resources/META-INF/hcm-module.yaml


group:
  name: myproject-test
project: myproject-test
module:
  name: test-config
          

Important: Do NOT include config: or after: sections. ModuleReader discovers config by directory convention.

2. Add HCM Configuration

Directory: src/test/resources/hcm-config/hst/

File: demo-hst.yaml


definitions:
  config:
    /hst:myproject:
      jcr:primaryType: hst:hst
    /hst:myproject/hst:sites:
      jcr:primaryType: hst:sites
    /hst:myproject/hst:sites/myproject:
      jcr:primaryType: hst:site
      hst:content: /content/documents/myproject
    /hst:myproject/hst:configurations:
      jcr:primaryType: hst:configurations
    /hst:myproject/hst:configurations/myproject:
      jcr:primaryType: hst:configuration
    /hst:myproject/hst:configurations/myproject/hst:sitemap:
      jcr:primaryType: hst:sitemap
    /hst:myproject/hst:configurations/myproject/hst:sitemap/root:
      jcr:primaryType: hst:sitemapitem
      hst:componentconfigurationid: hst:pages/homepage
    /hst:myproject/hst:configurations/myproject/hst:pages:
      jcr:primaryType: hst:pages
    /hst:myproject/hst:configurations/myproject/hst:pages/homepage:
      jcr:primaryType: hst:component
    /hst:myproject/hst:hosts:
      jcr:primaryType: hst:virtualhosts
          

Note: BRUT uses /hst:myproject as HST root (not /hst:hst) for test isolation.

3. Override Repository Bean

File: src/test/resources/org/example/config-service-jcr.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-4.1.xsd">

  <bean id="javax.jcr.Repository"
        class="org.bloomreach.forge.brut.resources.ConfigServiceRepository"
        init-method="init"
        destroy-method="close">
    <constructor-arg ref="cndResourcesPatterns"/>
    <constructor-arg ref="contributedCndResourcesPatterns"/>
    <constructor-arg ref="yamlResourcesPatterns"/>
    <constructor-arg ref="contributedYamlResourcesPatterns"/>
    <constructor-arg value="myproject"/>  <!-- project namespace -->
  </bean>

</beans>
          

4. Use in Your Test


@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MyIntegrationTest extends AbstractJaxrsTest {

    @Override
    protected List<String> contributeSpringConfigurationLocations() {
        return Arrays.asList(
            "/org/example/config-service-jcr.xml",  // ConfigServiceRepository override
            "/org/example/custom-jaxrs.xml",
            "/org/example/rest-resources.xml"
        );
    }

    @Override
    protected String contributeHstConfigurationRootPath() {
        return "/hst:myproject";  // BRUT uses project-specific root
    }

    @Test
    void testHstStructureCreated() throws Exception {
        Repository repo = getComponentManager().getComponent(Repository.class);
        Session session = repo.login(new SimpleCredentials("admin", "admin".toCharArray()));

        assertTrue(session.nodeExists("/hst:myproject"));
        assertTrue(session.nodeExists("/hst:myproject/hst:configurations/myproject"));
    }
}
          

How It Works

Architecture

ConfigServiceRepository uses ModuleReader for explicit module loading:


Test Resources (target/test-classes)
├── META-INF/hcm-module.yaml          <- Module descriptor
└── hcm-config/                       <- Config discovered by convention
    └── hst/*.yaml                    <- Your HST definitions
                 |
           ModuleReader                <- Loads module explicitly (no classpath scan)
                 |
    ConfigurationModelImpl.build()    <- Builds configuration model
                 |
      ConfigurationConfigService      <- brXM's production bootstrap service
                 |
          JCR Repository               <- Production-identical structure
          

Key Insight: We use ModuleReader.read(path, false) to load project test modules explicitly by path, and scan the classpath for addon modules (non-platform, non-project JARs with META-INF/hcm-module.yaml), avoiding ClasspathConfigurationModelReader which would include framework JARs with unmet dependencies.

Initialization Flow

  1. Register CNDs: Node type definitions from classpath
  2. Load Test Modules: ModuleReader finds META-INF/hcm-module.yaml in target/test-classes
  3. Auto-Discover Addon Modules: Scans classpath JARs for non-platform, non-project HCM modules; loaded before project modules
  4. Discover Config: ModuleReader automatically finds hcm-config/**/*.yaml
  5. Build Model: Creates ConfigurationModelImpl with only test modules
  6. Apply via ConfigService: Uses reflection to call package-private ConfigService methods
  7. Create JCR Nodes: ConfigService writes production-identical structure
  8. Import YAML Content: Additional content from YAML patterns
  9. Recalculate Paths: Update hippo:paths properties

HCM Module Format

Correct Format (ModuleReader)


# META-INF/hcm-module.yaml
group:
  name: test-group
project: test-project
module:
  name: test-config
  # NO 'config:' key here! ModuleReader discovers by convention.
          

ModuleReader automatically discovers:

  • hcm-config/**/*.yaml - Configuration
  • hcm-content/**/*.yaml - Content
  • namespaces/**/*.cnd - Node types

Common Mistakes


# WRONG - config: key is invalid for ModuleReader
module:
  name: test-config
  config:              # <- ERROR: Not valid!
    source: /hcm-config

# WRONG - Missing dependencies cause errors
group:
  name: test-group
  after:
    - hippo-cms       # <- ERROR: hippo-cms doesn't exist in test env
          

HCM Config Path Format

HCM config uses flat paths, not nested YAML:


# CORRECT - Flat paths
definitions:
  config:
    /hst:myproject:
      jcr:primaryType: hst:hst
    /hst:myproject/hst:sites:
      jcr:primaryType: hst:sites
    /hst:myproject/hst:sites/myproject:
      jcr:primaryType: hst:site

# WRONG - Nested structure (creates wrong paths)
definitions:
  config:
    /hst:myproject:
      jcr:primaryType: hst:hst
      /hst:sites:                    # Wrong: creates /hst:sites not /hst:myproject/hst:sites
        jcr:primaryType: hst:sites
          

Comparison: SkeletonRepository vs ConfigServiceRepository

Aspect SkeletonRepository ConfigServiceRepository
HST Bootstrap Minimal hardcoded structure Full production ConfigService
Maintenance Manual updates when brXM changes Automatic (uses production code)
Structure Basic hst:hst node only Complete HST tree
Setup Zero configuration Requires HCM module + config
Speed Faster (minimal setup) Slightly slower (full bootstrap)
Use Case Simple unit tests Integration tests needing real HST
Production Parity No Yes (exact same code path)

Troubleshooting

ParserException: Key 'config' is not allowed

Cause: Using ClasspathConfigurationModelReader format in hcm-module.yaml

Fix: Remove config: section. ModuleReader discovers by convention.

MissingDependencyException: missing dependency 'hippo-cms'

Cause: Module declares dependency on framework module not present in test

Fix: Remove after: section from group.

Addon module not loading

Cause: Module group name starts with hippo or onehippo, or matches your project group — auto-discovery filters it out

Fix: Use dependencyHcmModules = {"module-name"} to force-include it

Conflicting addon module

Cause: An auto-discovered addon module conflicts with your test setup

Fix: Use excludeDependencyHcmModules = {"module-name"} to opt out

HST nodes not created

Cause: HCM config files missing or in wrong location

Fix:

  • Verify hcm-config/ directory exists in test resources
  • Check YAML syntax
  • Ensure paths use /hst:myproject (not /hst:hst)

Bootstrap Strategy Pattern

ConfigServiceRepository uses pluggable strategies:

  • ConfigServiceBootstrapStrategy - Uses ConfigService (preferred)
    • Auto-detects via canHandle() - checks for META-INF/hcm-module.yaml
    • Loads modules explicitly with ModuleReader
  • ManualBootstrapStrategy - Minimal setup (fallback)
    • Always returns true for canHandle()
    • Creates basic /hst:hst node only

Strategy selection is automatic based on classpath resources.

Example Project Structure


src/test/
├── java/org/example/
│   └── MyIntegrationTest.java
└── resources/
    ├── META-INF/
    │   └── hcm-module.yaml              # Module descriptor
    ├── hcm-config/
    │   └── hst/
    │       └── demo-hst.yaml            # HST configuration
    └── org/example/
        └── config-service-jcr.xml       # Repository override