# Advanced OSGi: Factory Configurations, Service Ranking and Dynamic References

The Essentials series covered one component with one configuration and one service wired to one consumer. Real projects need more: one integration per brand, several handlers that all take part in a request, optional services that come and go. OSGi has patterns for each, and they're the foundation for the rest of this series. This post covers **factory configurations**, **multiple implementations with ranking**, and **dynamic references**, with their differences between AEM as a Cloud Service and 6.5.

## Factory Configurations: One Component, Many Instances

A normal configuration creates **one** component instance. A **factory configuration** creates **one instance per configuration file**. Use it when "the same thing" must exist several times with different settings, for example one API connection per brand.

Define the component as a factory:

```java
@ObjectClassDefinition(name = "My Project - Brand Settings")
public @interface BrandConfig {
    @AttributeDefinition(name = "Brand ID")
    String brandId();

    @AttributeDefinition(name = "API base URL")
    String apiBaseUrl();
}
```

```java
@Component(service = BrandSettings.class, configurationPolicy = ConfigurationPolicy.REQUIRE)
@Designate(ocd = BrandConfig.class, factory = true)
public class BrandSettingsImpl implements BrandSettings {

    private volatile BrandConfig config;

    @Activate
    @Modified
    protected void activate(BrandConfig config) {
        this.config = config;
    }

    public String brandId() { return config.brandId(); }
    public String apiBaseUrl() { return config.apiBaseUrl(); }
}
```

Then add one file per brand. The part after the tilde is just an instance name:

```plaintext
com.myproject.core.services.BrandSettingsImpl~brand-a.cfg.json
com.myproject.core.services.BrandSettingsImpl~brand-b.cfg.json
```

```json
{ "brandId": "brand-a", "apiBaseUrl": "https://a.example.com/api" }
```

`ConfigurationPolicy.REQUIRE` means **no configuration, no instance**. That is exactly what you want: adding a brand is adding a file, with no code change.

You can put different instances in different run-mode folders (for example a test brand only in `config.dev`), which is how you keep test data out of Production.

## Multiple Implementations: The Whiteboard Pattern

Often several components implement the **same interface** and all should take part, such as a set of validators, exporters or content processors. Instead of one consumer listing them by name, the consumer asks OSGi for **all** of them:

```java
public interface ContentProcessor {
    int priority();
    void process(Resource resource);
}
```

```java
@Component(service = ProcessingPipeline.class)
public class ProcessingPipeline {

    private final List<ContentProcessor> processors = new CopyOnWriteArrayList<>();

    @Reference(cardinality = ReferenceCardinality.MULTIPLE,
               policy = ReferencePolicy.DYNAMIC,
               policyOption = ReferencePolicyOption.GREEDY)
    protected void bindProcessor(ContentProcessor processor) {
        processors.add(processor);
    }

    protected void unbindProcessor(ContentProcessor processor) {
        processors.remove(processor);
    }

    public void run(Resource resource) {
        processors.stream()
                .sorted(Comparator.comparingInt(ContentProcessor::priority))
                .forEach(p -> p.process(resource));
    }
}
```

Every `ContentProcessor` service that appears gets passed to `bindProcessor`, and every one that goes away is passed to `unbindProcessor`. New processors in other bundles are picked up **without changing the pipeline**.

Points to notice:

- `MULTIPLE` **+** `DYNAMIC` means the pipeline keeps running as processors come and go
- `GREEDY` means a processor that appears *later* is still bound, rather than ignored
- `CopyOnWriteArrayList` makes iteration safe while another thread binds or unbinds
- The **sort** gives you a stable order, using your own priority value

## Service Ranking: Choosing One Winner

If a consumer needs just **one** implementation and several exist, OSGi chooses by `service.ranking` (highest wins; the default is 0). That lets a project override a default implementation without editing it:

```java
@Component(service = PriceService.class, property = {"service.ranking:Integer=100"})
public class ProjectPriceService implements PriceService { ... }
```

Use ranking for **overrides**. Use the whiteboard pattern when **all** implementations should run.

## Dynamic References and Safe Access

A **static** reference restarts your component when the service changes. A **dynamic** one keeps it running but means the service can vanish at any moment. The safe habits are:

- Mark the field `volatile`
- Read it **once** into a local variable, then test and use that
- Never keep a service in a long-lived static or cache
- Do any cleanup for a vanished service in `unbind`

```java
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
private volatile NotificationService notifier;

void tellSomeone(String message) {
    NotificationService n = notifier;
    if (n != null) {
        n.send(message);
    }
}
```

## Component Property Types

Instead of strings in a `property = {...}` array, you can use typed annotation properties, for example to give a servlet filter its ranking or pattern. They keep the property names checked by the compiler and reduce typos. Use them where your Sling or OSGi libraries provide them.

## AEM as a Cloud Service vs 6.5

| Topic | AEM 6.5 | AEM as a Cloud Service |
| --- | --- | --- |
| Factory configs: file naming | `.config` files use a hyphen in the name, and `sling:OsgiConfig` nodes can use a hyphen or tilde | `.cfg.json` with a **tilde**: `PID~name.cfg.json` |
| Adding an instance | Often by hand in the console | By committing a file and deploying |
| Per-environment instances | Custom run modes available | Only `author`, `publish` and `dev`, `stage`, `prod` |
| Several Publish instances | You manage them | Publish scales automatically. Your dynamic references must tolerate any instance starting at any time |
| Rolling updates | Planned outage or manual rollout | New and old code run **side by side** during an update, so a component must tolerate services appearing in any order |

The last point matters. On Cloud Service you can't assume a fixed startup order or that every instance has the same set of services at every instant. That's one more reason to favor dynamic, optional or multiple references wherever a missing service shouldn't break the feature.

## Common Pitfalls

- **Factory component never appears.** No configuration file for that run mode, or the file's PID is wrong
- **All instances get the same values.** The files share an instance name, so they overwrite each other. Give each a unique name after the tilde
- `ConcurrentModificationException` when iterating a dynamic list. Use a copy-on-write collection
- **A cached service becomes invalid.** Don't store dynamic services in statics
- **Order depends on startup timing.** Sort explicitly, and don't rely on the order of binding

## What's Next

Services and components cover how your code is built. Next we look at how it **hooks into requests and content changes**: **Sling filters** and **resource change listeners**.

---

*Next in this series: \[Sling Filters and Resource Change Listeners\]*
