> 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.

# Sling Model

## Table of Contents

| # | Content                                          |
| - | ------------------------------------------------ |
| 1 | What is a Sling Model, where it is used          |
| 2 | `@Model` + `adaptables` (Resource vs Request)    |
| 3 | Common injection annotations                     |
| 4 | `@Default`, optional injection, `@PostConstruct` |
| 5 | Interfaces + `adapters`                          |
| 6 | JSON export (`.model.json`)                      |
| 7 | Unit testing with `AemContext`                   |
| 8 | Service users + repoinit (non-request contexts)  |
| 9 | Debugging (adapt returns `null`, empty fields)   |

***

## 1. Role

| Concept           | Description                                                                                                                                  |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| **Sling Model**   | Java POJO adapted by Sling from a `Resource` or `SlingHttpServletRequest`, with fields **injected** automatically from JCR / request / OSGi. |
| **Why needed**    | Decouples content-reading logic from HTL; HTL only calls getters without repeating `resource.getValueMap().get(...)` across templates.       |
| **When required** | Most components with dialogs and logic should have a model (or delegate to another model).                                                   |

**Flow Summary (HTL):**

1. `data-sly-use` points to a class annotated with `@Model`.
2. Sling creates an instance and injects fields.
3. HTL uses `${model.property}`.

***

## 2. Minimal Model

**Java (`core` module, example `com.mysite.core.models.TitleModel`):**

```java
package com.mysite.core.models;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Default;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;

@Model(adaptables = Resource.class)
public class TitleModel {

    @ValueMapValue
    @Default(values = "Default Title")
    private String title;

    @ValueMapValue
    @Default(values = "h2")
    private String headingLevel;

    public String getTitle() { return title; }
    public String getHeadingLevel() { return headingLevel; }
}
```

**HTL:**

```html
<div data-sly-use.model="com.mysite.core.models.TitleModel">
    <h1 data-sly-element="${model.headingLevel}">${model.title}</h1>
</div>
```

| AEM 6.5 Note       | Details                                                                                                                           |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| Package            | `org.apache.sling.models.annotations.*` (Sling Models API included with AEM).                                                     |
| `data-sly-element` | Must return a valid tag name (`h1`–`h6`); if authors enter invalid tags, consider validating in model or falling back to default. |

***

## 3. `adaptables`: `Resource` vs `SlingHttpServletRequest`

| `adaptables`                    | Usage                                                                                     | Typical Injectors                                                                        |
| ------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `Resource.class`                | Only needs properties / child nodes on the content resource.                              | `@ValueMapValue`, `@ChildResource`, `@ResourcePath`, `@OSGiService`, `@Self` (Resource). |
| `SlingHttpServletRequest.class` | Needs request data: selectors, query parameters, `RequestDispatcher`, global HTL objects. | All above + `@RequestAttribute`, `@ScriptVariable`, `@Self` (Request).                   |

```java
// Preferred when sufficient — simple, testable, runs outside HTTP requests (if Resource available)
@Model(adaptables = Resource.class)
public class SimpleModel { }

@Model(adaptables = SlingHttpServletRequest.class)
public class RequestAwareModel { }
```

| Best Practice (6.5)                     | Content                                        |
| --------------------------------------- | ---------------------------------------------- |
| Prefer `Resource.class`                 | Fewer request dependencies, easy unit testing. |
| `SlingHttpServletRequest` when required | `Page`, `Style`, HTL parameters, etc.          |

***

## 4. Injection Annotations Review

### 4.1. `@ValueMapValue` — Reading Properties

```java
@ValueMapValue
private String title;

@ValueMapValue
private boolean featured;

@ValueMapValue
private java.util.Calendar publishDate;

@ValueMapValue(name = "jcr:title")
private String pageTitle;
```

| Commonly Supported Types                           | Notes                                                                                        |
| -------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| `String`, numbers, `boolean`/`Boolean`, `Calendar` | Default name = field name; use `name = "..."` for namespaced properties (`jcr:title`, etc.). |

### 4.2. `@ChildResource` — Child Nodes / Multifields

```java
@ChildResource
private java.util.List<org.apache.sling.api.resource.Resource> links;
```

**Multifield with Nested Model:**

```java
@Model(adaptables = Resource.class)
public class LinkItem {
    @ValueMapValue private String label;
    @ValueMapValue private String url;
    @ValueMapValue
    @Default(booleanValues = false)
    private boolean openInNewTab;
    // getters...
}

@Model(adaptables = Resource.class, defaultInjectionStrategy = org.apache.sling.models.annotations.DefaultInjectionStrategy.OPTIONAL)
public class NavigationModel {
    @ChildResource
    private java.util.List<LinkItem> links;
    public java.util.List<LinkItem> getLinks() {
        return links != null ? links : java.util.Collections.emptyList();
    }
}
```

| AEM Dialog                  | Note                                                                                  |
| --------------------------- | ------------------------------------------------------------------------------------- |
| `composite="{Boolean}true"` | Creates a **child node** per item — structure matches `@ChildResource` + child model. |
| Omitting `composite`        | May store as string/array — **cannot** map directly to `List<LinkItem>`.              |

### 4.3. `@Self` — The Adaptable Instance

```java
@Model(adaptables = Resource.class)
public class PathModel {
    @Self
    private Resource resource;
    public String getPath() { return resource.getPath(); }
}
```

Request-based: `@Self SlingHttpServletRequest request` — used for query parameters, etc.
**Security:** Request parameters are untrusted inputs; sanitize before using in queries, redirects, or authorizations.

### 4.4. `@OSGiService`

```java
@Model(adaptables = Resource.class)
public class ArticleListModel {
    @org.apache.sling.models.annotations.injectorspecific.OSGiService
    com.day.cq.search.QueryBuilder queryBuilder;
    @Self
    private Resource resource;
}
```

Requires an **active** OSGi service; injected interface must match registered type.

### 4.5. `@ScriptVariable` (Request Adaptable Only)

```java
@Model(adaptables = SlingHttpServletRequest.class)
public class PageHeaderModel {
    @org.apache.sling.models.annotations.injectorspecific.ScriptVariable
    private com.day.cq.wcm.api.Page currentPage;
}
```

### 4.6. `@RequestAttribute` — Parameters from HTL

```html
<div data-sly-use.model="${'com.mysite.core.models.ListModel' @ maxItems=5}">
```

```java
@Model(adaptables = SlingHttpServletRequest.class, defaultInjectionStrategy = org.apache.sling.models.annotations.DefaultInjectionStrategy.OPTIONAL)
public class ListModel {
    @org.apache.sling.models.annotations.injectorspecific.RequestAttribute
    @org.apache.sling.models.annotations.Default(intValues = 10)
    private int maxItems;
}
```

### 4.7. `@ResourcePath`

```java
@org.apache.sling.models.annotations.injectorspecific.ResourcePath(path = "/content/mysite/en/jcr:content")
private Resource siteRoot;
```

### 4.8. `@Via` — Injecting from Resource when Adaptable is Request

```java
@ValueMapValue
@org.apache.sling.models.annotations.Via("resource")
private String title;
```

***

## 5. `@Default` — Choosing the Right Attribute by Type

| Field Type              | `@Default` Attribute                                | Example                           |
| ----------------------- | --------------------------------------------------- | --------------------------------- |
| `String` / `String[]`   | `values`                                            | `@Default(values = "x")`          |
| `boolean`               | `booleanValues`                                     | `@Default(booleanValues = false)` |
| `int`                   | `intValues`                                         | `@Default(intValues = 10)`        |
| `long`                  | `longValues`                                        |                                   |
| `double`                | `doubleValues`                                      |                                   |
| Invalid Attribute Usage | Compiles successfully but defaults fail at runtime. |                                   |

***

## 6. `@PostConstruct` — Post-Injection Initialization

**AEM 6.5:** uses `javax.annotation.PostConstruct` (Java 8 and 11 on AEM 6.5 stack).

```java
import javax.annotation.PostConstruct;

@Model(adaptables = Resource.class, defaultInjectionStrategy = org.apache.sling.models.annotations.DefaultInjectionStrategy.OPTIONAL)
public class ArticleModel {
    @ValueMapValue private String text;
    private int readingTime;

    @PostConstruct
    protected void init() {
        if (text != null) {
            int wordCount = text.split("\\s+").length;
            readingTime = Math.max(1, wordCount / 200);
        }
    }
    public int getReadingTime() { return readingTime; }
}
```

| Common Use Cases           | Example                                   |
| -------------------------- | ----------------------------------------- |
| Derived values             | `readingTime`, formatted strings.         |
| Validation / normalization | Trimming strings, complex fallback logic. |

***

## 7. Optional Injection

| Approach                 | Description                                                                                          |
| ------------------------ | ---------------------------------------------------------------------------------------------------- |
| Per-field                | `@ValueMapValue(injectionStrategy = org.apache.sling.models.annotations.InjectionStrategy.OPTIONAL)` |
| Model-wide (Recommended) | `defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL` on `@Model`                           |

| 6.5 Practice               | Recommendation                                                      |
| -------------------------- | ------------------------------------------------------------------- |
| Authors leave fields blank | `OPTIONAL` + `@Default` reduces `adaptTo(...) == null` occurrences. |
| Mandatory business fields  | Validate inside `@PostConstruct` or HTL `data-sly-test`.            |

***

## 8. Interfaces + `adapters` (Clean API for HTL & Testing)

```java
public interface Hero {
    String getHeading();
    String getSubheading();
    String getImagePath();
}
```

```java
@Model(
    adaptables = Resource.class,
    adapters = Hero.class,
    defaultInjectionStrategy = org.apache.sling.models.annotations.DefaultInjectionStrategy.OPTIONAL
)
public class HeroImpl implements Hero {
    @ValueMapValue private String heading;
    @ValueMapValue private String subheading;
    @ValueMapValue(name = "fileReference") private String imagePath;
    @Override public String getHeading() { return heading; }
    @Override public String getSubheading() { return subheading; }
    @Override public String getImagePath() { return imagePath; }
}
```

**HTL using interface:**

```html
<div data-sly-use.hero="com.mysite.core.models.Hero">
    <h1>${hero.heading}</h1>
</div>
```

| Advantages               |                        |
| ------------------------ | ---------------------- |
| Testing                  | Easy to mock `Hero`.   |
| Swapping implementations | HTL remains untouched. |

***

## 9. JSON Export (Headless / SPA) — `.model.json`

**Requirements Overview:**

| Component      | Description                                                                                                                                                                 |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `adapters`     | Includes `com.adobe.cq.export.json.ComponentExporter` (AEM)                                                                                                                 |
| `resourceType` | Matches component `sling:resourceType`                                                                                                                                      |
| `@Exporter`    | Typically uses `name = "jackson"`, `extensions = "json"`, `selector = "model"` — bundle requires compatible Jackson exporter dependency (pre-configured in AEM archetypes). |
| JSON Content   | Exported fields depend on model getters, `com.adobe.cq.export.json` version, and serializer settings — align with project `core/pom.xml`.                                   |

Skeleton Example:

```java
@Model(
    adaptables = Resource.class,
    adapters = { Hero.class, com.adobe.cq.export.json.ComponentExporter.class },
    resourceType = "mysite/components/hero",
    defaultInjectionStrategy = org.apache.sling.models.annotations.DefaultInjectionStrategy.OPTIONAL
)
@org.apache.sling.models.annotations.exporter.Exporter(
    name = "jackson",
    extensions = "json",
    selector = "model"
)
public class HeroImpl implements Hero, com.adobe.cq.export.json.ComponentExporter {

    @ValueMapValue
    private String heading;

    @Override
    public String getExportedType() {
        return "mysite/components/hero";
    }

    public String getHeading() { return heading; }
}
```

| AEM 6.5 Note |                                                                                                               |
| ------------ | ------------------------------------------------------------------------------------------------------------- |
| URL          | Call on component instance **resource**, e.g.: `.../jcr:content/.../hero.model.json`.                         |
| Quick Check  | Test on Author/Publish instance; if 404, check `resourceType` and `com.adobe.cq.export.json` package imports. |

***

## 10. Unit Testing (wcm.io Aem Mocks, JUnit 5)

| Element    |                                                                                               |
| ---------- | --------------------------------------------------------------------------------------------- |
| Dependency | `io.wcm:io.wcm.testing.aem-mock.junit5` (project BOM).                                        |
| Pattern    | `AemContext` + `context.create().resource(...)` + `resource.adaptTo(InterfaceOrModel.class)`. |

```java
import io.wcm.testing.aem.junit5.AemContext;
import io.wcm.testing.aem.junit5.AemContextExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.apache.sling.api.resource.Resource;

import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith(AemContextExtension.class)
class HeroImplTest {
    private final AemContext context = new AemContext();

    @Test
    void headingFromValueMap() {
        context.create().resource("/content/test",
            "sling:resourceType", "mysite/components/hero",
            "heading", "Hello",
            "subheading", "AEM 6.5");
        Resource r = context.resourceResolver().getResource("/content/test");
        Hero h = r.adaptTo(Hero.class);
        assertEquals("Hello", h.getHeading());
    }
}
```

***

## 11. Quick Troubleshooting

| Symptom                              | Resolution                                                                                                    |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
| `adaptTo(Model)` returns `null`      | Incorrect `adaptables`; missing `defaultInjectionStrategy` + missing property; OSGi model registration error. |
| Field is `null`                      | Match JCR property name with annotation `name`; property unsaved after dialog save.                           |
| `List` multifield empty              | Verify `composite` structure + child node path.                                                               |
| `@OSGiService` is null / model fails | OSGi service inactive; interface mismatch.                                                                    |
| Works locally, fails on deploy       | Missing `core` bundle package, or missing OSGi configuration.                                                 |

***

## 12. Non-Request Contexts: Service Users + Repoinit (On-Prem 6.5)

| Problem                                       | Solution                                                                                  |
| --------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Schedulers / events / workflows lack requests | Use `ResourceResolverFactory.getServiceResourceResolver(...)` with mapped **subservice**. |
| Creating users & ACLs programmatically        | Use `RepositoryInitializer` (repoinit) in `ui.config` — versioned in source control.      |
| _Do NOT do_                                   | Open `admin` sessions in app code.                                                        |

**Repoinit Example Configuration:**

`ui.config/.../org.apache.sling.jcr.repoinit.RepositoryInitializer~mysite.cfg.json`

```json
{
  "scripts": [
    "create service user mysite-reader",
    "set ACL for mysite-reader",
    "  allow jcr:read on /content/mysite",
    "end"
  ]
}
```

**Service User Mapping:**

`org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~mysite.cfg.json`

```json
{
  "user.mapping": [
    "com.mysite.core:mysite-reader=mysite-reader"
  ]
}
```

**Code:**

```java
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
// ...
Map<String, Object> auth = Map.of(ResourceResolverFactory.SUBSERVICE, "mysite-reader");
try (ResourceResolver rr = resolverFactory.getServiceResourceResolver(auth)) {
    // use rr, always use try-with-resources
}
```

| On-Prem vs Cloud | Note                                                                                |
| ---------------- | ----------------------------------------------------------------------------------- |
| **AEM 6.5**      | Can create users manually on dev; **production** should use repoinit + ACL as code. |
| **AEMaaCS**      | Cannot create users via CRXDE on prod; mandatory configuration approach.            |

***

## 13. Security Summary

| Principle          |                                                                 |
| ------------------ | --------------------------------------------------------------- |
| Request parameters | Validate / whitelist inputs.                                    |
| Resolvers          | Close custom `ResourceResolver` instances (try-with-resources). |
| Permissions        | Use minimum service user permissions; avoid `admin`.            |
| Logging            | Avoid logging sensitive data / PII.                             |

***

## 14. Summary

- Sling Model = **Java class** annotated with `@Model` for Sling to **inject** values from JCR/request/OSGi, invoked in HTL via `data-sly-use`.
- **Prefer** `Resource.class` when sufficient; use `SlingHttpServletRequest` when global HTL objects or request data are needed.
- Use **`DefaultInjectionStrategy.OPTIONAL`** + `@Default` for stable authoring environments.
- Use **Interfaces + `adapters`** to separate API definitions and simplify testing.
- **`.model.json`**: Requires `ComponentExporter` + `@Exporter` + `resourceType`.
- Outside HTTP requests: Use **service user + repoinit + mapping**; avoid admin sessions.

***

## Internal Links (aem-docs)

- [HTL Templates](https://nhanbe.id.vn/aem/foundation/htl-templates.md)
- [Component Dialogs](https://nhanbe.id.vn/aem/foundation/component-dialogs.md)
- [Your First Component](https://nhanbe.id.vn/aem/foundation/your-first-component.md)
- [OSGi Fundamentals](https://nhanbe.id.vn/aem/foundation/osgi-fundamentals.md)
