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

Sling Model là POJO được adapt từ một `Resource` hoặc `SlingHttpServletRequest`. Toàn bộ việc map dữ liệu JCR vào field Java được điều khiển bằng annotation. Note này tập trung vào từng annotation: nó làm gì, inject nguồn nào, và usecase code thực tế trong AEM 6.5.

## Phân loại annotation

Có ba nhóm chính. Hiểu đúng nhóm thì đọc code Sling Model không còn mơ hồ.

| Nhom        | Annotation tieu bieu                                                                                                | Vai tro                                                                    |
| ----------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
| Class-level | `@Model`                                                                                                            | Khai bao class la Sling Model, dinh nghia adaptable, adapter, resourceType |
| Injector    | `@ValueMapValue`, `@ChildResource`, `@Self`, `@SlingObject`, `@OSGiService`, `@ScriptVariable`, `@RequestAttribute` | Quyet dinh lay du lieu tu nguon nao                                        |
| Modifier    | `@Default`, `@Named`, `@Optional`, `@Required`, `@Via`, `@Source`                                                   | Tinh chinh cach inject hoat dong                                           |
| Lifecycle   | `@PostConstruct`                                                                                                    | Chay logic sau khi inject xong                                             |

```mermaid
flowchart LR
  R[Resource hoac Request] -->|adaptTo| M[Sling Model]
  M --> I1[Injector: lay du lieu]
  I1 --> MOD[Modifier: tinh chinh]
  MOD --> PC[PostConstruct: xu ly]
  PC --> HTL[HTL doc getter]
```

## @Model

Đặt ở class. Bắt buộc khai báo `adaptables`. Các tham số còn lại định nghĩa hành vi adaptation.

```java title="TeaserModel.java"
package com.myproject.core.models;

import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;

@Model(
    adaptables = Resource.class,
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class TeaserModel {
    // fields
}
```

Tham số quan trọng:

| Tham so                    | Y nghia                                                                     |
| -------------------------- | --------------------------------------------------------------------------- |
| `adaptables`               | Nguon adapt: `Resource.class`, `SlingHttpServletRequest.class`, hoac ca hai |
| `adapters`                 | Interface ma model nay implement, dung khi adaptTo theo interface           |
| `resourceType`             | Gan model voi sling:resourceType, cho phep HTL `data-sly-use` tu resolve    |
| `defaultInjectionStrategy` | `OPTIONAL` (mac dinh nen dung) hoac `REQUIRED`                              |

:::tip
Luôn đặt `defaultInjectionStrategy = OPTIONAL` ở class level. Khi REQUIRED mà một field thiếu dữ liệu, toàn bộ adaptTo trả về null và component vỡ. OPTIONAL giúp model resilient với content thiếu property.
:::

### adaptables: Resource và Request

Chọn adaptable quyết định injector nào dùng được.

```java title="TeaserModel.java"
// Chi adapt tu Resource: khong dung duoc request-scoped injector
@Model(adaptables = Resource.class)
public class SimpleModel { }

// Adapt tu Request: dung duoc @RequestAttribute, request parameter, current request
@Model(adaptables = SlingHttpServletRequest.class)
public class RequestAwareModel { }

// Adapt tu ca hai: linh hoat nhung field request-scoped chi co data khi adapt tu request
@Model(adaptables = { Resource.class, SlingHttpServletRequest.class })
public class HybridModel { }
```

### adapters và resourceType

Pattern interface + impl giúp tách hợp đồng (interface) khỏi cài đặt, đồng thời cho HTL tự resolve qua resourceType.

```java title="TeaserModel.java"
@Model(
    adaptables = Resource.class,
    adapters = Teaser.class,
    resourceType = "myproject/components/teaser",
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
public class TeaserImpl implements Teaser {

    @ValueMapValue
    private String title;

    @Override
    public String getTitle() {
        return title;
    }
}
```

HTL chỉ cần `data-sly-use`, Sling tự chọn impl đúng theo resourceType.

```html
<sly data-sly-use.teaser="com.myproject.core.models.Teaser">
  <h2>${teaser.title}</h2>
</sly>
```

## @ValueMapValue

Injector dùng nhiều nhất. Đọc một property từ `ValueMap` của resource hiện tại. Đây là cách lấy dữ liệu author nhập trong dialog.

```java title="TextModel.java"
@Model(adaptables = Resource.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class TextModel {

    // Lay property "jcr:title" -> field title (cung ten)
    @ValueMapValue
    private String title;

    // Lay property "text"
    @ValueMapValue
    private String text;

    // Property ten khong trung field -> dung @Named
    @Named("fileReference")
    @ValueMapValue
    private String imagePath;

    // Gia tri mac dinh khi author chua nhap
    @ValueMapValue
    @Default(values = "h2")
    private String headingTag;

    // Checkbox boolean trong dialog
    @ValueMapValue
    @Default(booleanValues = false)
    private boolean showTitle;

    // Multifield/multi-value property
    @ValueMapValue
    private String[] tags;

    public String getTitle() { return title; }
    public String getText() { return text; }
    public String getImagePath() { return imagePath; }
    public String getHeadingTag() { return headingTag; }
    public boolean isShowTitle() { return showTitle; }
    public String[] getTags() { return tags; }
}
```

Usecase: bất kỳ component nào có dialog. Field name nên trùng property name để bỏ `@Named`.

## @ChildResource

Inject một child node thành `Resource`, hoặc adapt thẳng child thành Sling Model khác. Dùng khi dialog lưu dữ liệu dạng node con (ví dụ multifield lưu thành các node con).

```java
@Model(adaptables = Resource.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class CardModel {

    // Inject child node "image" thanh Resource
    @ChildResource
    private Resource image;

    // Inject child node va adapt thang sang model khac
    @ChildResource
    private LinkModel link;

    // Child multifield: node "items" chua nhieu node con cung type
    @ChildResource
    private List<ListItemModel> items;

    public Resource getImage() { return image; }
    public LinkModel getLink() { return link; }
    public List<ListItemModel> getItems() { return items; }
}
```

Phân biệt với `@ValueMapValue`: `@ValueMapValue` đọc property của chính node hiện tại, còn `@ChildResource` đi xuống node con.

## @Self

Adapt chính adaptable hiện tại sang một type khác. Hay dùng nhất là inject `SlingHttpServletRequest` hoặc adapt request sang một model khác.

```java
@Model(adaptables = SlingHttpServletRequest.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ButtonModel {

    // Lay chinh request dang xu ly
    @Self
    private SlingHttpServletRequest request;

    // Adapt chinh adaptable hien tai sang model khac de tai su dung logic
    @Self
    private CommonProperties commonProperties;

    public String getCurrentPath() {
        return request.getResource().getPath();
    }
}
```

Usecase điển hình: model adapt từ request nhưng muốn dùng lại một model adapt từ resource. `@Self` cho phép compose model.

## @SlingObject

Inject các object chuẩn của Sling mà không cần khai báo thủ công. Sling biết sẵn cách cung cấp theo type.

```java
@Model(adaptables = SlingHttpServletRequest.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ContextModel {

    @SlingObject
    private ResourceResolver resourceResolver;

    @SlingObject
    private Resource currentResource;

    @SlingObject
    private SlingHttpServletRequest request;

    @SlingObject
    private SlingHttpServletResponse response;

    public Resource lookup(String path) {
        return resourceResolver.getResource(path);
    }
}
```

Các type hỗ trợ: `ResourceResolver`, `Resource` (current resource), `SlingHttpServletRequest`, `SlingHttpServletResponse`. Dùng để truy vấn JCR, resolve path khác, đọc request.

## @OSGiService

Inject một OSGi service vào model. Đây là cầu nối giữa Sling Model (presentation logic) và business service.

```java
@Model(adaptables = Resource.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ProductModel {

    @OSGiService
    private ProductService productService;

    // Loc theo property cua service implementation cu the
    @OSGiService(filter = "(component.name=com.myproject.core.impl.CachedProductService)")
    private ProductService cachedProductService;

    @ValueMapValue
    private String sku;

    private Product product;

    @PostConstruct
    protected void init() {
        if (sku != null) {
            product = productService.findBySku(sku);
        }
    }

    public Product getProduct() { return product; }
}
```

Usecase: gọi service đọc DB, call API ngoài, hoặc lấy config. Lưu ý không nhồi business logic nặng vào model, chỉ orchestrate gọi service.

## @ScriptVariable

Inject các binding variable của HTL/Sightly. Chỉ hoạt động khi model adapt từ request. Tránh việc HTL phải truyền tham số thủ công.

```java
@Model(adaptables = SlingHttpServletRequest.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class PageInfoModel {

    @ScriptVariable
    private Page currentPage;

    @ScriptVariable
    private ValueMap pageProperties;

    @ScriptVariable
    private Design currentDesign;

    @ScriptVariable
    private WCMMode wcmmode;

    public String getPageTitle() {
        return currentPage.getTitle();
    }

    public boolean isEditMode() {
        return WCMMode.EDIT.equals(wcmmode);
    }
}
```

Các binding hay dùng: `currentPage`, `currentDesign`, `pageProperties`, `wcmmode`, `resource`, `properties`. Tương đương các biến global trong HTL nhưng dùng được ở Java.

## @RequestAttribute

Đọc attribute được set trên request, thường do `data-sly-call` truyền vào hoặc do servlet/filter set trước đó.

```java
@Model(adaptables = SlingHttpServletRequest.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ListItemModel {

    @RequestAttribute
    private int index;

    @RequestAttribute(name = "itemData")
    private String data;

    public int getIndex() { return index; }
    public String getData() { return data; }
}
```

HTL truyền attribute qua template call:

```html
<sly data-sly-use.item="${'com.myproject.core.models.ListItemModel' @ index=itemIndex, itemData=raw}" />
```

Usecase: component cha render list, truyền index hoặc dữ liệu từng phần tử xuống model con.

## @Default

Modifier. Cung cấp giá trị mặc định khi nguồn inject không có dữ liệu. Phải đi kèm một injector.

```java
@ValueMapValue
@Default(values = "Read more")
private String linkText;

@ValueMapValue
@Default(intValues = 3)
private int columns;

@ValueMapValue
@Default(booleanValues = true)
private boolean enabled;

@ValueMapValue
@Default(values = { "tag1", "tag2" })
private String[] defaultTags;
```

Lưu ý kiểu giá trị phải khớp: `values` cho String, `intValues`, `longValues`, `booleanValues`, `doubleValues`, `floatValues`, `shortValues`. Sai kiểu thì default không áp dụng.

## @Named

Modifier. Map field tới một property/attribute có tên khác với tên field. Cần khi tên JCR không hợp lệ làm tên biến Java hoặc khác convention.

```java
// Property JCR la "jcr:title" -> khong dat ten field "jcr:title" duoc
@Named("jcr:title")
@ValueMapValue
private String title;

// Property "fileReference" -> field ten ngan gon hon
@Named("fileReference")
@ValueMapValue
private String image;
```

## @Optional và @Required

Modifier override `defaultInjectionStrategy` ở mức field. Khi class đặt OPTIONAL, dùng `@Required` để bắt buộc một field cụ thể, và ngược lại.

```java
@Model(adaptables = Resource.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class FormModel {

    // Bat buoc: thieu thi adaptTo tra ve null
    @ValueMapValue
    @Required
    private String actionPath;

    // Optional (theo default class), du khong khai bao @Optional
    @ValueMapValue
    private String successMessage;
}
```

Hành vi REQUIRED: nếu field không inject được, toàn bộ model adaptTo trả về null. Đó là lý do nên để class OPTIONAL và chỉ Required field thật sự sống còn.

## @Via

Modifier. Đổi đối tượng nguồn trước khi inject. Dùng nhiều nhất khi adapt từ request nhưng muốn inject property của resource bên dưới.

```java
@Model(adaptables = SlingHttpServletRequest.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ViaModel {

    // Request khong co ValueMap property; @Via tro toi resource cua request
    @ValueMapValue
    @Via("resource")
    private String title;
}
```

Không có `@Via("resource")`, injector cố đọc property trên request và trả về null. `@Via` chuyển nguồn sang `request.getResource()`. Đây là pattern then chốt khi viết model lai request/resource.

## @Source

Modifier hiếm dùng trực tiếp, nhưng quan trọng để hiểu. Khi nhiều injector cùng có thể xử lý một field, `@Source` chỉ định injector nào được chọn. Hầu hết annotation injector (như `@ValueMapValue`, `@OSGiService`) đã được gắn sẵn `@Source` bên trong, nên thường không phải viết tay.

```java
// @ValueMapValue da chua @Source("valuemap") nen khong can them
@ValueMapValue
private String title;

// Truong hop hiem: dung @Inject thuan va chi dinh source
@Inject
@Source("script-bindings")
private Page currentPage;
```

Quy tắc: ưu tiên dùng annotation injector cụ thể (`@ValueMapValue`, `@ScriptVariable`...) thay vì `@Inject` + `@Source`. Code rõ ràng và ít nhập nhằng hơn.

## @Inject

Injector tổng quát, thử lần lượt mọi injector theo thứ tự ranking cho tới khi có giá trị. Linh hoạt nhưng mơ hồ và chậm hơn. Hiện nay nên tránh, ưu tiên injector cụ thể.

```java
// Cach cu: mo ho, Sling phai do tim injector phu hop
@Inject
private String title;

// Cach nen dung: ro rang nguon du lieu
@ValueMapValue
private String title;
```

:::tip
Trong code mới luôn dùng injector cụ thể thay cho `@Inject`. Khi đọc một field, người sau biết ngay dữ liệu đến từ ValueMap, child resource, OSGi service hay script binding. `@Inject` che giấu thông tin đó.
:::

## @PostConstruct

Lifecycle. Method gắn `@PostConstruct` chạy một lần sau khi mọi field đã được inject. Đây là nơi đặt logic khởi tạo, biến đổi dữ liệu, gọi service.

```java
@Model(adaptables = SlingHttpServletRequest.class,
       defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public class ArticleModel {

    @ValueMapValue
    private String fileReference;

    @SlingObject
    private ResourceResolver resolver;

    private String imageAlt;
    private boolean hasImage;

    @PostConstruct
    protected void init() {
        hasImage = StringUtils.isNotBlank(fileReference);
        if (hasImage) {
            Resource asset = resolver.getResource(fileReference);
            if (asset != null) {
                imageAlt = asset.getValueMap().get("jcr:title", String.class);
            }
        }
    }

    public boolean isHasImage() { return hasImage; }
    public String getImageAlt() { return imageAlt; }
}
```

Quy tắc method `@PostConstruct`: không tham số, trả về void, nên để `protected` hoặc `private`. Mọi field inject đã sẵn sàng tại thời điểm này.

## @Exporter

Biến Sling Model thành JSON endpoint qua model.json selector. Nền tảng cho SPA Editor và headless. Dùng cùng Jackson exporter.

```java
@Model(
    adaptables = SlingHttpServletRequest.class,
    adapters = { Teaser.class, ComponentExporter.class },
    resourceType = "myproject/components/teaser",
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(name = "jackson", extensions = "json")
public class TeaserImpl implements Teaser {

    @ValueMapValue
    @JsonProperty("title")
    private String title;

    @ValueMapValue
    @JsonIgnore
    private String internalNote;

    @Override
    public String getTitle() { return title; }
}
```

Gọi `path/to/component.model.json` trả về JSON của model. `@JsonProperty` đổi tên field trong output, `@JsonIgnore` loại field khỏi JSON.

## Bảng tra cứu nhanh

| Annotation          | Nguon du lieu                         | Adaptable yeu cau     | Dung khi                             |
| ------------------- | ------------------------------------- | --------------------- | ------------------------------------ |
| `@ValueMapValue`    | Property cua resource hien tai        | Resource hoac Request | Doc gia tri dialog                   |
| `@ChildResource`    | Node con                              | Resource hoac Request | Multifield, node con, nested model   |
| `@Self`             | Chinh adaptable, adapt sang type khac | Bat ky                | Compose model, lay request           |
| `@SlingObject`      | Object chuan Sling                    | Bat ky                | ResourceResolver, currentResource    |
| `@OSGiService`      | OSGi service                          | Bat ky                | Goi business service                 |
| `@ScriptVariable`   | HTL binding                           | Request               | currentPage, wcmmode, pageProperties |
| `@RequestAttribute` | Request attribute                     | Request               | Nhan data tu data-sly-call           |
| `@Inject`           | Do tim moi injector                   | Bat ky                | Tranh dung, uu tien injector cu the  |

| Modifier    | Tac dung                                             |
| ----------- | ---------------------------------------------------- |
| `@Default`  | Gia tri mac dinh khi thieu du lieu                   |
| `@Named`    | Map field toi ten property khac                      |
| `@Optional` | Field khong bat buoc (override class REQUIRED)       |
| `@Required` | Field bat buoc (override class OPTIONAL)             |
| `@Via`      | Doi nguon truoc khi inject, vd request sang resource |
| `@Source`   | Chi dinh injector cu the                             |

## Lỗi thường gặp

| Trieu chung                          | Nguyen nhan                                  | Cach sua                                         |
| ------------------------------------ | -------------------------------------------- | ------------------------------------------------ |
| adaptTo tra ve null                  | Mot field REQUIRED khong inject duoc         | Dat class OPTIONAL, chi Required field can thiet |
| Field luon null khi adapt tu request | Quen `@Via("resource")` cho `@ValueMapValue` | Them `@Via("resource")`                          |
| `@ScriptVariable` null               | Model adapt tu Resource thay vi Request      | Doi adaptables sang Request                      |
| HTL khong tu resolve model           | Thieu `resourceType` trong `@Model`          | Khai bao resourceType khop sling:resourceType    |
| Property "jcr:title" khong vao       | Ten field khong hop le                       | Dung `@Named("jcr:title")`                       |

:::tip
Khi debug model trả null, mở `/system/console/status-slingmodels` để xem model nào đã đăng ký với resourceType nào, và check log Felix với DEBUG cho package `org.apache.sling.models`. Phần lớn lỗi là sai adaptable hoặc thiếu Via.
:::
