> For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt.

# OSGi Configuration

## 1. Configuration Convention

OSGi configuration must be deployed via code (content package), not manually edited in Felix Console.

- Easy PR review
- Git history tracking
- Consistency across environments
- Prevents configuration drift from manual operations

Felix Console is for quick debugging only; any changes must be "converted" back to `.cfg.json` files in the repository.

## 2. `ui.config` Directory Structure and Run Modes

Standard location:

```text
ui.config/
└── src/main/content/jcr_root/apps/<app>/osgiconfig/
    ├── config/                 # all
    ├── config.author/          # author only
    ├── config.publish/         # publish only
    ├── config.author.dev/      # author + dev
    ├── config.author.stage/    # author + stage
    ├── config.author.prod/     # author + prod
    ├── config.publish.dev/     # publish + dev
    ├── config.publish.stage/   # publish + stage
    └── config.publish.prod/    # publish + prod
```

### Resolution Rule

AEM selects configuration based on specificity level from highest to lowest. When the same PID appears in multiple folders, the more specific folder takes precedence.

Example: If the same PID is placed in `config/` and `config.author.dev/`, Author running with run modes `author,dev` will use the one from `config.author.dev/`.

### Run Mode Folder Case Sensitivity

Use lowercase only. `config.Author/` will be ignored.

## 3. File Naming Rules (PID)

Use `.cfg.json` for all files.

### Singleton Configuration

Single configuration instance:

```text
com.myproject.core.services.impl.MyServiceImpl.cfg.json
```

### Factory Configuration

Multiple instances, use `~<id>`:

```text
com.myproject.core.services.impl.MyServiceImpl~site-a.cfg.json
com.myproject.core.services.impl.MyServiceImpl~site-b.cfg.json
```

`<id>` is for differentiation only and does not affect runtime logic.

### Most Dangerous Error: PID Typo

One incorrect character in the filename = configuration is silently ignored. Prevention methods:

- Copy-paste the FQCN of component/`@Designate` PID from code
- Add CI checks (see Section 10)

## 4. Data Types in `.cfg.json`

| JSON    | OSGi             | Example                     |
| ------- | ---------------- | --------------------------- |
| string  | `String`         | `"https://api.example.com"` |
| boolean | `Boolean`        | `true`                      |
| number  | `Integer`/`Long` | `100`                       |
| float   | `Float`/`Double` | `3.14`                      |
| array   | `String[]`       | `["jpg","png"]`             |

Complete example:

```json
{
  "enabled": true,
  "syncIntervalMinutes": 30,
  "endpoint": "https://dam-api.example.com/v2",
  "allowedMimeTypes": [
    "image/jpeg",
    "image/png",
    "application/pdf"
  ],
  "maxRetries": 3,
  "connectionTimeoutMs": 5000,
  "verboseLogging": false
}
```

## 5. Typed Configuration (AEM 6.5 Standard)

Objective: Clear schema, UI support in `/system/console/configMgr`, type-safe injection.

### 5.1. `@ObjectClassDefinition`

```java
package com.myproject.core.config;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

@ObjectClassDefinition(
    name = "MyProject - Asset Sync Service",
    description = "Controls the remote DAM synchronisation service."
)
public @interface AssetSyncConfig {

    @AttributeDefinition(
        name = "Enabled",
        description = "Enable or disable the sync service."
    )
    boolean enabled() default false;

    @AttributeDefinition(
        name = "Sync Interval (minutes)",
        description = "How often the sync job runs."
    )
    int syncIntervalMinutes() default 30;

    @AttributeDefinition(
        name = "Remote Endpoint",
        description = "Base URL of the remote DAM API."
    )
    String endpoint() default "https://dam-api.example.com/v2";

    @AttributeDefinition(
        name = "Allowed MIME Types",
        description = "List of MIME types that will be synchronised."
    )
    String[] allowedMimeTypes() default { "image/jpeg", "image/png" };

    @AttributeDefinition(
        name = "Max Retries",
        description = "Number of retry attempts on failure."
    )
    int maxRetries() default 3;
}
```

### 5.2. `@Designate` + `@Activate/@Modified`

```java
package com.myproject.core.services.impl;

import com.myproject.core.config.AssetSyncConfig;
import com.myproject.core.services.AssetSyncService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(service = AssetSyncService.class, immediate = true)
@Designate(ocd = AssetSyncConfig.class)
public class AssetSyncServiceImpl implements AssetSyncService {

    private static final Logger LOG = LoggerFactory.getLogger(AssetSyncServiceImpl.class);

    private volatile boolean enabled;
    private volatile int syncIntervalMinutes;
    private volatile String endpoint;
    private volatile String[] allowedMimeTypes;
    private volatile int maxRetries;

    @Activate
    @Modified
    protected void activate(AssetSyncConfig config) {
        this.enabled = config.enabled();
        this.syncIntervalMinutes = config.syncIntervalMinutes();
        this.endpoint = config.endpoint();
        this.allowedMimeTypes = config.allowedMimeTypes();
        this.maxRetries = config.maxRetries();

        LOG.info("AssetSync enabled={}, interval={}m, endpoint={}",
            enabled, syncIntervalMinutes, endpoint);
    }

    @Override
    public void sync() {
        if (!enabled) {
            return;
        }
        // sync logic...
    }
}
```

Add `@Modified` for AEM to apply configuration at runtime without bundle restart.

## 6. Secret Values on AEM 6.5 (on-prem)

Do not commit plaintext secrets to Git.

Common approach on AEM 6.5:

1. Navigate to `http://<host>:4502/system/console/crypto`
2. Encrypt secret → obtain string like `\{enc:...\}`
3. Place `\{enc:...\}` value in `.cfg.json`

Operational notes:

- The crypto key is instance-specific → migrating encrypted strings to another instance may fail decryption; often need to re-encrypt per environment.
- In production, manage secrets through operational processes (vault/secrets manager + automation). If not available, crypto console is the minimum baseline.

## 7. Felix Console—Use for Observation, Not "Production Configuration"

Commonly used URLs:

| URL                          | Purpose                                 |
| ---------------------------- | --------------------------------------- |
| `/system/console/configMgr`  | View/edit runtime configuration (debug) |
| `/system/console/bundles`    | Bundle status                           |
| `/system/console/components` | DS component status                     |
| `/system/console/services`   | OSGi services list                      |

If edited in the console, configuration resides in JCR (typically under `/apps/system/config`) and will be **overwritten** on the next deployment.

## 8. Common AEM Configuration PIDs (AEM 6.5)

| Service                | PID                                                      | Common Adjustments           |
| ---------------------- | -------------------------------------------------------- | ---------------------------- |
| Sling Referrer Filter  | `org.apache.sling.security.impl.ReferrerFilter`          | Allowed hosts, allow empty   |
| Externalizer           | `com.day.cq.commons.impl.ExternalizerImpl`               | Domain mappings per run mode |
| Sling Logging          | `org.apache.sling.commons.log.LogManager`                | Level, file, pattern         |
| Sling Logger (factory) | `org.apache.sling.commons.log.LogManager.factory.config` | Per-package log level        |
| CSRF Filter            | `com.adobe.granite.csrf.impl.CSRFFilter`                 | Excluded paths               |
| CORS Policy            | `com.adobe.granite.cors.impl.CORSPolicyImpl`             | Origins/methods/headers      |

## 9. Pitfalls and Deployment Checklist

Pitfalls:

- Wrong run mode folder (placing publish config in `config.author/`)
- Missing `~id` for factory configuration
- PID typo (configuration ignored)
- Missing `.cfg.json`
- `filter.xml` does not include `/apps/<app>/osgiconfig`

Checklist:

- [ ] Files are in correct run mode folder
- [ ] Filename PID is exactly correct
- [ ] Factory configuration includes `~<id>`
- [ ] Package filter includes `.../osgiconfig` path
- [ ] After deployment, verify configuration is applied in `/system/console/configMgr`

## 10. Validate Configuration in CI (Reduce Production Errors)

Objective: Catch PID typos before deployment to any environment.

Practical approach:

- Parse filenames under `ui.config/.../osgiconfig/**`
- For singleton: get PID = filename (remove `.cfg.json`)
- For factory: get PID = part before `~`
- Match PID against:
  - `@Component` FQCN (if using class-based PID)
  - or `@Designate(ocd=...)` PID as defined by project convention

Failing the build when any PID does not match (or is not on the whitelist) significantly reduces "configuration not applied" errors.

## References

- [OSGi Metatype Annotations](https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.metatype.html)
