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

# AEM Cheat Sheet

## 1. Quick URLs & Operations

### Console URLs

| URL                                                                     | Description                                          |
| ----------------------------------------------------------------------- | ---------------------------------------------------- |
| `/system/console/bundles`                                               | Bundle list and states (Active, Resolved, Installed) |
| `/system/console/components`                                            | OSGi Declarative Services — Active or Unsatisfied    |
| `/system/console/configMgr`                                             | OSGi configuration in effect                         |
| `/system/console/services`                                              | The OSGi service registry and service rankings       |
| `/system/console/servletresolver`                                       | Which servlet handles a given URL                    |
| `/system/console/status-adapters`                                       | Registered Sling adapter factories                   |
| `/system/console/slinglog`                                              | Logger configuration and log levels                  |
| `/system/console/tracer`                                                | Sling Log Tracer for request-level tracing           |
| `/system/console/depfinder`                                             | Which bundle exports or imports a package            |
| `/system/console/jmx`                                                   | MBeans — Oak, replication queues, index stats        |
| `/system/console/healthcheck`                                           | System health checks                                 |
| `/system/console/slingevent`                                            | Sling job queues, topics, and failure counters       |
| `/system/console/status-Threads`                                        | Thread dump                                          |
| `/system/console/status-slingsettings`                                  | Active Sling run modes and instance settings         |
| `/crx/de`                                                               | CRXDE Lite repository browser                        |
| `/crx/packmgr`                                                          | Package Manager                                      |
| `/etc/replication/agents.author`                                        | Replication agents (AEM 6.5)                         |
| `/libs/cq/search/content/querydebug.html`                               | QueryBuilder debugger                                |
| `/libs/granite/operations/content/diagnosistools/queryPerformance.html` | Query performance and Oak index analysis             |
| `/useradmin`                                                            | AEM Security & User Admin console                    |
| `/myworkflows`                                                          | AEM Workflow console                                 |

### Log Locations

| Location                          | Description                                                 |
| --------------------------------- | ----------------------------------------------------------- |
| `crx-quickstart/logs/error.log`   | Application errors, stack traces, and OSGi bundle messages  |
| `crx-quickstart/logs/request.log` | Request execution time and response codes                   |
| `crx-quickstart/logs/access.log`  | HTTP request source IP, timestamp, and HTTP method          |
| `crx-quickstart/logs/stdout.log`  | JVM standard out and startup console messages               |
| `<apache>/logs/dispatcher.log`    | Dispatcher cache hit/miss, filter rules, and flush requests |
| Cloud Service                     | Cloud Manager → Download Logs, hoặc `aio aem:rde:logs`      |

### URL Debug Parameters

| Parameter                                          | Description                                                                     |
| -------------------------------------------------- | ------------------------------------------------------------------------------- |
| `?wcmmode=(edit\|preview\|design\|disabled)`       | Đặt WcmMode để test hiển thị component theo từng mode                           |
| `?debugClientLibs=true`                            | Xuất toàn bộ clientlib categories thành từng file JS/CSS riêng lẻ chưa minified |
| `?debugClientLibs=true&debugClientLibsAppend=true` | Gộp tất cả JS/CSS vào 1 file duy nhất để debug                                  |
| `?debug=messages`                                  | Hiển thị Sling log message trực tiếp trên trang                                 |
| `?tags=<tag>`                                      | Lọc debug message theo tag cụ thể khi dùng kèm `?debug=messages`                |

:::warning\[Classic UI Parameters]
`?debug=layout` và `#cf/` (content-finder) chỉ hoạt động trong Classic UI (đã deprecated từ AEM 6.4), không có hiệu lực trong Touch UI.
:::

### Instance Management (AEM 6.5)

```bash
# Khởi động instance (yêu cầu Java 8 hoặc Java 11)
java -Xmx2048m -jar aem-author-p4502.jar

# Quản lý process qua script
crx-quickstart/bin/start
crx-quickstart/bin/stop
crx-quickstart/bin/status
```

**Run Modes cố định:** `author`, `publish`, `samplecontent`, `nosamplecontent` (được khóa sau lần đầu cài đặt).

- Đặt tên file quickstart theo convention: `cq-<instance-type>-p<port>.jar` (Ví dụ: `cq-author-p4502.jar`, `cq-publish-p4503.jar`).
- Cấu hình run modes bổ sung:
  1. `crx-quickstart/conf/sling.properties` → `sling.run.modes=author,dev`
  2. Tham số dòng lệnh: `java -jar cq-author-p4502.jar -r dev`
  3. JVM System property: `-Dsling.run.modes=publish,prod`

**JVM Debug & Troubleshooting:**

```bash
# Dump heap tự động khi OutOfMemoryError
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/aem-heap.hprof

# Bật remote debug (IDE kết nối vào port 8000)
java -Xmx2048m -agentlib:jdwp=transport=dt_socket,address=8000,server=y,suspend=n -jar aem-author-p4502.jar

# Hoặc dùng start script có sẵn
crx-quickstart/bin/start -d --debug-port 8000
```

### Maven Commands

| Command                                               | Description                                        |
| ----------------------------------------------------- | -------------------------------------------------- |
| `mvn clean install -PautoInstallSinglePackage`        | Deploy package đầy đủ lên Author (port 4502)       |
| `mvn clean install -PautoInstallSinglePackagePublish` | Deploy package đầy đủ lên Publish (port 4503)      |
| `mvn clean install -PautoInstallBundle -pl core`      | Chỉ build và deploy bundle OSGi `core` (rất nhanh) |
| `mvn clean install -PautoInstallPackage -pl ui.apps`  | Chỉ build và deploy package `ui.apps`              |

### FileVault (`vlt`) & `filter.xml`

```bash
# Checkout path từ JCR về local disk
vlt --credentials admin:admin co http://localhost:4502/crx /apps/myproject ~/myproject

# Checkout với filter file cụ thể
vlt co --filter filter.xml http://localhost:4502/crx/-/jcr:root <local-path>

# Export / Import JCR data
vlt export -v http://localhost:4502/crx /content/forms forms
vlt -v import http://localhost:4502/crx . /

# Quản lý thay đổi
vlt st              # Xem status file đã thay đổi
vlt diff file.html  # Xem diff với JCR
vlt ci file.html    # Commit thay đổi lên JCR
vlt add file.html   # Đưa file mới vào tracking
vlt revert file.html# Khôi phục file local từ JCR
```

**Các mode của `filter.xml`:**

```xml
<filter root="/apps/myproject" mode="replace"/>
<filter root="/conf/myproject" mode="merge"/>
<filter root="/content/myproject" mode="update"/>
```

- **replace** _(Mặc định)_: Xóa toàn bộ nội dung cũ và ghi đè nội dung package. Node không tồn tại trong package sẽ bị xóa. Dùng cho code path (`/apps`).
- **merge**: Chỉ thêm node mới; không xóa hay sửa node đã tồn tại. Dùng cho `/conf` và `/content`.
- **update**: Cập nhật thuộc tính của node có sẵn và thêm node mới; không xóa node con.
- **exclude**: Loại trừ path cụ thể khỏi filter root.

***

## 2. JCR & Architecture Principles

### Common JCR Properties

Chi tiết xem tại: [JCR Properties](https://nhanbe.id.vn/aem/foundation/jcr-page-properties.md)

| Property                  | Description                                                                         |
| ------------------------- | ----------------------------------------------------------------------------------- |
| `sling:resourceType`      | Trỏ đến path của component render node này (ví dụ: `myproject/components/page`)     |
| `sling:resourceSuperType` | Kế thừa logic render và dialog từ component cha                                     |
| `cq:template`             | Path template được dùng để khởi tạo page này                                        |
| `jcr:primaryType`         | JCR node type (`cq:Page`, `cq:PageContent`, `nt:unstructured`, `cq:Component`, ...) |
| `jcr:title`               | Tiêu đề của page hoặc component                                                     |
| `cq:allowedTemplates`     | Regex array định nghĩa template được phép tạo dưới nhánh path này                   |
| `cq:isContainer`          | Đánh dấu node này có thể chứa component con                                         |
| `sling:configRef`         | Trỏ một content tree đến thư mục cấu hình `/conf` tương ứng                         |
| `allowProxy`              | Cho phép truy cập clientlib dưới `/apps` thông qua `/etc.clientlibs/`               |
| `categories`              | Định danh clientlib category để include trong HTL                                   |
| `embed`                   | Gộp nội dung clientlib khác vào chung một file                                      |
| `dependencies`            | Ép clientlib phụ thuộc phải được load trước                                         |

### David's Model (7 nguyên tắc thiết kế JCR)

1. **Data First, Structure Later. Maybe.** — Ưu tiên lưu trữ dữ liệu trước, đừng over-engineer cấu trúc phân cấp phức tạp ngay từ đầu.
2. **Drive the content hierarchy, don't let it happen.** — Chủ động thiết kế cây thư mục content có mục đích và phân tầng rõ ràng.
3. **Workspaces are for clone(), merge() and update().** — Workspace trong JCR dùng cho versioning/merge, không dùng để chia tách môi trường (dev/stage/prod).
4. **Beware of Same Name Siblings.** — Tránh dùng SNS (các node cùng tên dưới cùng cha) vì làm chậm query và khó bảo trì.
5. **References considered harmful.** — JCR reference tạo liên kết chặt giữa các node; hãy ưu tiên dùng path nếu có thể.
6. **Files are Files are Files.** — Lưu binary/tệp tin với đúng node type `nt:file` và `nt:resource`.
7. **ID's are evil.** — Hạn chế phụ thuộc vào UUID; sử dụng cấu trúc path tự nhiên có ý nghĩa.

### Sling URL to Script Resolution

URL format: `http://localhost:4502/content/sample/page.print.a4.html`

Sling resolve script theo `resourceType`, `selectors`, `extension`, và HTTP `method`. Script nào khớp selector cụ thể hơn sẽ được ưu tiên.

**Thứ tự ưu tiên match script (từ cụ thể nhất đến fallback):**

1. `print/a4.html` — Khớp cả 2 selectors `print` và `a4`
2. `print.html` — Khớp selector đầu tiên `print`
3. `a4.html` — Không hợp lệ nếu thiếu selector `print` đứng trước
4. `sample.html` — Khớp tên base của component (`sling:resourceType` = `.../sample`)
5. `html.html` — Fallback theo extension
6. `GET.html` — Fallback theo HTTP method

:::tip\[Resolution Rules]

- Sling tìm script trong `/apps` trước `/libs`. **Không bao giờ sửa code trực tiếp trong `/libs`**, luôn tạo overlay trong `/apps`.
- OSGi Servlet (`@SlingServletResourceTypes`) luôn có độ ưu tiên cao hơn JCR HTL script khi cùng resourceType + selector + extension.
- Path-based servlet (`@SlingServletPaths`) chạy trực tiếp theo URL path, bỏ qua toàn bộ cơ chế resource-type resolution.
  :::

### Sling Resource Merger (Overlay trong `/apps`)

Dùng để tùy biến hoặc ẩn các node/properties kế thừa từ `/libs` mà không sửa đổi file gốc:

```xml
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="nt:unstructured"
    sling:hideProperties="[title,description]"
    sling:hideChildren="[oldTab]"
    sling:hideResource="{Boolean}true"
    sling:orderBefore="targetNode"/>
```

- `sling:hideProperties` (`String | String[]`): Ẩn property cụ thể từ resource cha. Dùng `*` để ẩn toàn bộ.
- `sling:hideResource` (`Boolean`): Ẩn hoàn toàn node hiện tại và tất cả node con. **Bắt buộc dùng kiểu Boolean `{Boolean}true`**, nếu dùng String `"true"` sẽ không có tác dụng.
- `sling:hideChildren` (`String | String[]`): Ẩn các child node được chỉ định nhưng vẫn giữ lại node hiện tại.
- `sling:orderBefore` (`String`): Đặt vị trí node này đứng trước một sibling node xác định.

***

## 3. Component & Dialog Development

### Component Folder Structure

```tree
/apps/<project>/components/<name>/
├── .content.xml          # Component metadata (cq:Component)
├── <name>.html           # HTL script chính (trùng tên với folder)
├── _cq_dialog/
│   └── .content.xml      # Granite UI dialog cấu hình authoring
├── _cq_editConfig.xml    # Hành vi toolbar, listeners, inplace editing
├── _cq_template/
│   └── .content.xml      # Giá trị khởi tạo mặc định khi drop component
└── clientlibs/           # Clientlib riêng của component (nếu có)
```

### Component `.content.xml` Properties

| Property                          | Description                                                              |
| --------------------------------- | ------------------------------------------------------------------------ |
| `jcr:primaryType="cq:Component"`  | Đánh dấu node là một AEM Component                                       |
| `jcr:title`                       | Tên hiển thị của component trong Sidekick / Component Browser            |
| `jcr:description`                 | Tooltip giải thích mục đích component                                    |
| `componentGroup`                  | Tên group phân loại. Đặt `.hidden` nếu muốn ẩn khỏi author               |
| `sling:resourceSuperType`         | Kế thừa logic render và dialog từ component khác (ví dụ: Core Component) |
| `cq:isContainer="{Boolean}true"`  | Đánh dấu component là container có thể drop các component khác vào       |
| `cq:noDecoration="{Boolean}true"` | Bỏ thẻ wrapper `<div>` bao quanh component khi render                    |

### Dialog Skeleton (Granite Coral UI)

```tree
cq:dialog (sling:resourceType="cq/gui/components/authoring/dialog")
  └ content   → granite/ui/components/coral/foundation/container
     └ items
        └ tabs   → granite/ui/components/coral/foundation/tabs
           └ items
              └ mainTab (jcr:title="Properties") → .../foundation/container
                 └ items
                    └ columns → .../foundation/fixedcolumns
                       └ items
                          └ column → .../foundation/container
                             └ items
                                └ <form fields>
```

### Form Field Resource Types & Properties

**Prefix chung:** `granite/ui/components/coral/foundation/form/`

- `textfield` · `textarea` · `pathfield` · `select` · `checkbox` · `switch`
- `numberfield` · `datepicker` · `radiogroup` · `hidden` · `fileupload`
- `multifield` · `colorfield`
- Rich Text: `cq/gui/components/authoring/dialog/richtext`
- Tag Picker: `cq/gui/components/coral/common/form/tagfield`

**Common Field Properties:**

```xml
name="./propertyName"         <!-- Luôn có tiền tố ./ để lưu vào node hiện tại -->
fieldLabel="Title"            <!-- Nhãn hiển thị -->
fieldDescription="Enter text" <!-- Hướng dẫn cho tác giả -->
required="{Boolean}true"      <!-- Bắt buộc nhập -->
emptyText="Placeholder"       <!-- Văn bản gợi ý -->
value="true"                  <!-- Giá trị khi checkbox được chọn -->
uncheckedValue="false"        <!-- Luôn đặt cả value và uncheckedValue cho checkbox -->
rootPath="/content/dam"       <!-- Giới hạn đường dẫn cho pathfield -->
composite="{Boolean}true"     <!-- Đánh dấu multifield lưu node dạng composite -->
```

### Dialog Validation (Granite UI)

**1. Đăng ký validator trong Clientlib (`cq.authoring.dialog`):**

```javascript
(function ($, $document) {
    "use strict";
    $.validator.register({
        selector: "[data-validation='myproject.productcode']",
        validate: function (el) {
            var val = el.val();
            if (!val) { return; } // Bỏ qua nếu rỗng, để required kiểm tra
            if (!/^[A-Z]{2}-\d{4}$/.test(val)) {
                return "Định dạng mã sản phẩm phải là AB-1234";
            }
        }
    });
})(jQuery, jQuery(document));
```

**2. Khai báo trên dialog field XML:**

```xml
<productCode
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
    fieldLabel="Product Code"
    name="./productCode"
    required="{Boolean}true"
    validation="myproject.productcode">
    <granite:data
        jcr:primaryType="nt:unstructured"
        maxLength="10"/>
</productCode>
```

**3. Cross-field Revalidation (JavaScript):**

```javascript
$document.on("change", "[name='./startDate']", function () {
    $(this).closest("form").find("[name='./endDate']").trigger("change");
});
```

**4. Server-side Validation đối ứng trong Sling Model:**

```java
@PostConstruct
protected void init() {
    this.valid = (productCode != null && PATTERN.matcher(productCode).matches());
    if (productCode != null && !valid) {
        LOG.warn("Invalid productCode '{}' at {} - validation bypassed", productCode, resource.getPath());
    }
}
```

### Conditional Show/Hide trong Dialog

**Dropdown Show/Hide:**

```xml
<!-- Trigger Field: Select dropdown -->
<type
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="Display Type"
    name="./type">
    <granite:data
        jcr:primaryType="nt:unstructured"
        cq-dialog-dropdown-showhide-target=".cta-target-container"/>
    <items jcr:primaryType="nt:unstructured">
        <internal jcr:primaryType="nt:unstructured" text="Internal" value="internal"/>
        <external jcr:primaryType="nt:unstructured" text="External" value="external"/>
    </items>
</type>

<!-- Target Field: Container hiển thị khi value khớp -->
<externalUrl
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
    fieldLabel="External URL"
    name="./externalUrl">
    <granite:data
        jcr:primaryType="nt:unstructured"
        showhidetargetvalue="external"/>
</externalUrl>
```

_Checkbox tương tự với class:_ `cq-dialog-checkbox-showhide` và target `.target-class`.

### Page Properties Visibility

```xml
cq:showOnCreate="{Boolean}true"   <!-- Hiển thị trong wizard Create Page -->
cq:hideOnEdit="{Boolean}true"     <!-- Ẩn field khi đang ở màn hình Edit Page Properties -->
renderReadOnly="{Boolean}true"    <!-- Chỉ xem, không cho chỉnh sửa khi edit -->
allowBulkEdit="{Boolean}true"     <!-- Cho phép sửa hàng loạt khi chọn nhiều page -->
```

### EditConfig (`cq:EditConfig`)

File: `_cq_editConfig.xml`

```xml
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="cq:EditConfig"
    cq:actions="[text:Component,-,edit,delete,insert,copymove]"
    cq:emptyText="Nhấp đôi để cấu hình Component">
    <cq:listeners
        jcr:primaryType="cq:EditListenersConfig"
        afteredit="REFRESH_PAGE"
        afterinsert="REFRESH_PAGE"/>
    <cq:inplaceEditing
        jcr:primaryType="cq:InplaceEditingConfig"
        active="{Boolean}true"
        editorType="plaintext"/>
</jcr:root>
```

- `cq:actions`: `text:<label>` | `-` (dấu phân cách) | `edit` | `delete` | `insert` | `copymove`
- `cq:listeners`: `afteredit` / `afterinsert` = `REFRESH_PAGE` hoặc `REFRESH_SELF`
- `cq:inplaceEditing`: `editorType="text|title|plaintext"`

### Accessibility Contract (Accordion Component)

```html
<button aria-expanded="false" aria-controls="panel-1" id="btn-1">
  ${item.title}
</button>
<div id="panel-1" role="region" aria-labelledby="btn-1" hidden>
  ${item.content @ context='html'}
</div>

<!-- Icons minh họa -->
<span class="icon" aria-hidden="true"></span>

<!-- Thông báo động -->
<div role="status" aria-live="polite"></div>
```

***

## 4. HTL (Sightly)

### Calling Sling Models

```html
<!-- Gọi model theo class name -->
<sly data-sly-use.cta="com.myproject.core.models.CtaModel"/>
<h1>${cta.title}</h1>               <!-- Gọi getTitle() -->
<a href="${cta.link}">${cta.text}</a>
<div data-sly-test="${cta.ready}">...</div> <!-- Gọi isReady() -->

<!-- Truyền tham số vào Model -->
<sly data-sly-use.list="${'com.myproject.core.models.ListModel' @ rootPath='/content/pages', limit=5}"/>
```

### Block Statements

| Statement                                          | Purpose                                                                  |
| -------------------------------------------------- | ------------------------------------------------------------------------ |
| `data-sly-use.name="..."`                          | Khởi tạo Sling Model, Java use-class, hoặc HTL template                  |
| `data-sly-set.name="${...}"`                       | Gán biến tạm thời trong scope                                            |
| `data-sly-test="${...}"`                           | Render thẻ khi điều kiện đúng (if)                                       |
| `data-sly-test.name="${...}"`                      | Kiểm tra điều kiện và lưu kết quả vào biến để tái sử dụng                |
| `data-sly-list.item="${list}"`                     | Vòng lặp: lặp qua các phần tử con của collection                         |
| `data-sly-repeat.item="${list}"`                   | Vòng lặp: lặp lại chính thẻ HTML chứa nó                                 |
| `data-sly-unwrap`                                  | Bỏ thẻ HTML bao ngoài, chỉ render nội dung bên trong                     |
| `data-sly-resource="${path @ resourceType='...'}"` | Include một JCR resource khác vào trang                                  |
| `data-sly-include="script.html"`                   | Include một file HTL/JSP khác vào vị trí hiện tại                        |
| `data-sly-template.name="${@ param}"`              | Định nghĩa template có thể tái sử dụng                                   |
| `data-sly-call="${name @ param=value}"`            | Gọi template đã định nghĩa                                               |
| `data-sly-element="${model.tag}"`                  | Đổi tên thẻ HTML động (ví dụ: `h1`, `h2`, `div`)                         |
| `data-sly-attribute.href="${model.url}"`           | Gán thuộc tính HTML động                                                 |
| `data-sly-text="${model.content}"`                 | Thay thế toàn bộ text bên trong thẻ                                      |
| `<sly>`                                            | Thẻ ẩn dùng làm container cho các logic directive mà không sinh thẻ HTML |

**Loop Variables (tự động sinh ra khi lặp):**

```text
itemList.index    # Index 0-based
itemList.count    # Thứ tự 1-based
itemList.first    # Boolean: phần tử đầu tiên
itemList.last     # Boolean: phần tử cuối cùng
itemList.middle   # Boolean: phần tử ở giữa
itemList.odd      # Boolean: vị trí lẻ (1, 3, 5)
itemList.even     # Boolean: vị trí chẵn (2, 4, 6)
```

### Display Contexts & Expressions

```html
${model.richText @ context='html'}         <!-- Cho phép thẻ HTML (RichText) -->
${model.targetUrl @ context='uri'}          <!-- Escape URI / URL an toàn -->
${model.jsonSchema @ context='unsafe'}      <!-- Không escape (chỉ dùng cho JSON-LD/schema đã serialize) -->
${model.jsString @ context='scriptString'}  <!-- An toàn khi chèn vào script JS -->
${'{0} of {1}' @ format=[item.count, total]}<!-- Format string -->
${items @ join=', '}                        <!-- Ghép mảng chuỗi -->
${'Learn More' @ i18n}                      <!-- Dịch đa ngôn ngữ qua i18n dictionary -->
${model.date @ format='dd MMM yyyy'}        <!-- Format ngày tháng -->
```

### Global Objects

```text
properties            # ValueMap của resource hiện tại
pageProperties        # ValueMap của jcr:content của page hiện tại
inheritedPageProperties # ValueMap kế thừa từ các page cha
currentPage           # Đối tượng Page hiện tại
currentStyle          # Resolved Policy của component hiện tại
resource              # Resource hiện tại
resourceResolver      # ResourceResolver hiện tại
wcmmode               # Chế độ WCM (wcmmode.edit, wcmmode.preview, wcmmode.disabled)
request               # SlingHttpServletRequest
```

### Common Patterns

```html
<!-- If / Else Pattern -->
<sly data-sly-test.hasItems="${model.items.size > 0}">
    <ul><li data-sly-repeat="${model.items}">${item.title}</li></ul>
</sly>
<sly data-sly-test="${!hasItems}">
    <p>Không có dữ liệu hiển thị.</p>
</sly>

<!-- Placeholder khi ở chế độ Edit -->
<sly data-sly-test="${!model.ready && wcmmode.edit}"
     data-sly-resource="${'' @ resourceType='wcm/core/components/placeholder'}"/>

<!-- Truy cập property có ký tự đặc biệt (dấu hai chấm) -->
${properties['jcr:title']}

<!-- Điều khiển Link Checker -->
<a x-cq-linkchecker="valid" href="${cta.link}">Link luôn hợp lệ</a>
<a x-cq-linkchecker="skip" href="${cta.link}">Bỏ qua kiểm tra link</a>

<!-- Comment trong HTL -->
<!--/* Comment này bị loại bỏ hoàn toàn, không render ra HTML client */-->
<!-- Comment này sẽ gửi về browser client -->
```

***

## 5. Client-Side Libraries (Clientlibs)

### Cấu trúc thư mục & `.content.xml`

```tree
/apps/<project>/clientlibs/clientlib-site/
├── .content.xml
├── css.txt
├── js.txt
├── css/
│   ├── base.less
│   └── components.less
├── js/
│   └── site.js
└── resources/       # Static assets (fonts, images) phục vụ trực tiếp
```

File `.content.xml`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="cq:ClientLibraryFolder"
    categories="[myproject.site]"
    allowProxy="{Boolean}true"
    dependencies="[myproject.dependencies,core.wcm.components.commons.site]"
    embed="[myproject.grid]"/>
```

- `categories`: Tên định danh clientlib để gọi trong HTL.
- `allowProxy="{Boolean}true"`: Cho phép truy cập clientlib dưới `/apps` thông qua URL `/etc.clientlibs/`.
- `dependencies`: Clientlib phụ thuộc, tải thành các file riêng biệt trước clientlib này.
- `embed`: Nhúng toàn bộ mã nguồn của clientlib được chỉ định vào trong cùng file này.

### Định dạng `css.txt` / `js.txt`

```text
#base=css
variables.less
base.less
components.less
```

### Proxy Path Translation

```text
Thư mục gốc JCR:   /apps/myproject/clientlibs/clientlib-site
       ↓ (Tự động bỏ tiền tố /apps)
URL phục vụ:       /etc.clientlibs/myproject/clientlibs/clientlib-site.css
Static Resources:  /etc.clientlibs/myproject/clientlibs/clientlib-site/resources/fonts/custom.woff2
```

### Include trong HTL

```html
<!-- Chuẩn AEM 6.5 và AEMaaCS (Core Components) -->
<sly data-sly-use.clientlib="core/wcm/components/commons/v1/templates/clientlib.html"/>

<sly data-sly-call="${clientlib.css @ categories='myproject.site'}"/> <!-- Đặt trong <head> -->
<sly data-sly-call="${clientlib.js  @ categories='myproject.site'}"/> <!-- Đặt cuối <body> -->
<sly data-sly-call="${clientlib.all @ categories='myproject.site'}"/> <!-- Render cả CSS và JS -->

<!-- Các tùy chọn loading -->
<sly data-sly-call="${clientlib.js @ categories='myproject.site', async=true}"/>
<sly data-sly-call="${clientlib.js @ categories='myproject.site', defer=true}"/>
<sly data-sly-call="${clientlib.css @ categories='myproject.print', media='print'}"/>
```

### Dispatcher Rule & Debug Consoles

**Dispatcher allow rule:**

```text
/0100 { /type "allow" /path "/etc.clientlibs/*" /extension '(css|js|png|jpg|svg|woff|woff2)' }
```

**Debug URLs:**

- `?debugClientLibs=true`: Tải từng file lẻ không nén để xem lỗi JS/CSS cụ thể.
- `/libs/granite/ui/content/dumplibs.html`: Danh sách toàn bộ clientlib trên hệ thống.
- `/libs/granite/ui/content/dumplibs.test.html?categories=myproject.site`: Kiểm tra resolve các file CSS/JS của category.
- `/libs/granite/ui/content/dumplibs.validate.html`: Tìm kiếm các category bị trùng lặp.
- `/libs/granite/ui/content/dumplibs.rebuild.html`: Xóa cache và build lại toàn bộ clientlibs.

***

## 6. Editable Templates & Policies

### Architecture Paths

```tree
/conf/<project>/settings/wcm/templates/<name>/
├── jcr:content        # Status (draft|enabled), allowedPaths, jcr:title
├── structure/         # Cấu trúc cố định, LIVE update đến tất cả page sử dụng template
├── initial/           # Nội dung khởi tạo, chỉ copy 1 lần khi tạo page mới
└── policies/          # Mapping từ cấu trúc component đến cq:policy tương ứng

/conf/<project>/settings/wcm/policies/       # Nơi lưu trữ POLICY DEFINITIONS
/conf/<project>/settings/wcm/template-types/ # Blueprint mẫu để tạo template mới
```

### Template Properties & Availability

**Properties trên `jcr:content` của template:**

- `jcr:title`: Tên template hiển thị khi tạo trang.
- `status`: `draft` | `enabled` | `disabled`. Phải là `enabled` thì tác giả mới tạo được trang.
- `allowedPaths`: Regex array xác định cây thư mục được phép dùng template (Ví dụ: `[/content/myproject(/.*)?]`).
- `ranking`: Thứ tự sắp xếp trong danh sách Create Page.

**Thứ tự ưu tiên đánh giá Template Availability:**

1. `cq:allowedTemplates` trên page cha hoặc site root (Độ ưu tiên cao nhất).
2. `allowedPaths` trên template definition.
3. `allowedParents` trên template.
4. `allowedChildren` trên template.

### Policy Properties

- `cq:policy`: Đường dẫn trỏ đến node định nghĩa policy trong `/conf/.../policies/`.
- `components`: Danh sách component được phép thêm vào container (Ví dụ: `["group:My Project", "myproject/components/content/hero"]`).
- `cq:styleGroups`: Định nghĩa Style System class cho component.
- `clientlibs`: Khai báo category clientlib tự động load cho page policy.

### Diagnostic Checklists

:::tip\[Checklist: Template không xuất hiện khi tạo page]

1. Template đã chuyển sang trạng thái `enabled` chưa?
2. `allowedPaths` regex có khớp với đường dẫn page đang tạo không?
3. Thuộc tính `cq:allowedTemplates` trên content root folder có bao gồm template này không?
4. User hiện tại có quyền JCR read trên thư mục `/conf/<project>` không?
   :::

:::tip\[Checklist: Không thêm được component vào layout container]

1. Component đã được tích chọn trong `components` list của container policy chưa?
2. `componentGroup` của component có bị rỗng hoặc là `.hidden` không?
3. Layout container con có bị thiếu ánh xạ `cq:policy` không?
4. Author có quyền modify trên node page đang edit không?
   :::

***

## 7. Sling Models

### `@Model` Annotation

```java
@Model(
    adaptables = { Resource.class, SlingHttpServletRequest.class },
    adapters = { Cta.class, ComponentExporter.class },
    resourceType = "myproject/components/cta",
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL,
    cache = true
)
public class CtaImpl implements Cta {
    // ...
}
```

- `adaptables`: `Resource.class`, `SlingHttpServletRequest.class`, hoặc cả hai.
- `adapters`: Interface đăng ký để inject hoặc export.
- `resourceType`: Liên kết trực tiếp model với component resource type.
- `defaultInjectionStrategy`: Mặc định là `REQUIRED`, nên đặt `OPTIONAL` để tránh NPE khi thiếu property.
- `cache = true`: Tái sử dụng instance model trong cùng một request.

### Injector Annotations

| Annotation          | Description                                                                                                                         |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `@ValueMapValue`    | Inject property từ chính node hiện tại                                                                                              |
| `@ChildResource`    | Inject child node thành `Resource`, một Model con, hoặc `List<Model>`                                                               |
| `@ResourcePath`     | Resolve path lưu trong property thành một `Resource`                                                                                |
| `@OSGiService`      | Inject một OSGi service vào model                                                                                                   |
| `@ScriptVariable`   | Inject các HTL binding objects (`currentPage`, `currentStyle`, `resourceResolver`) — yêu cầu adaptable là `SlingHttpServletRequest` |
| `@RequestAttribute` | Inject attribute được set từ filter hoặc servlet cấp cao hơn                                                                        |
| `@SlingObject`      | Inject `ResourceResolver`, `Resource`, `SlingHttpServletRequest`, `SlingHttpServletResponse`                                        |
| `@Self`             | Inject chính object adaptable (Resource hoặc Request)                                                                               |

### Modifiers & Lifecycle

```java
@ValueMapValue
@Named("jcr:title")                     // Tên property khác với tên field Java
private String title;

@ValueMapValue
@Default(values = "default-theme")      // Giá trị mặc định khi property null
private String theme;

@ValueMapValue
@Required                               // Bắt buộc property này không được null
private String requiredId;

@Self
@Via("resource")                        // Chuyển đối tượng resolve về resource khi adaptable là request
private Resource currentResource;

@OSGiService(filter = "(component.name=myproject.SpecialService)")
private MyService specificService;      // Lọc OSGi service cụ thể theo LDAP filter

@PostConstruct
protected void init() {
    // Chạy ngay sau khi hoàn thành injection. Bắt buộc: void, không tham số
}
```

### Usage & Testing

```html
<!-- HTL -->
<sly data-sly-use.model="com.myproject.core.models.CtaModel"/>
```

```java
// Java adaptation
CtaModel model = resource.adaptTo(CtaModel.class);
if (model != null) {
    // Logic xử lý
}
```

```java
// Unit Test setup với AemContext (io.wcm.testing.aem-mock)
context.load().json("/sample-content.json", "/content/sample");
context.addModelsForClasses(CtaImpl.class);
context.registerService(MyService.class, mockService);

Cta cta = context.currentResource("/content/sample/jcr:content/cta").adaptTo(Cta.class);
assertNotNull(cta);
```

***

## 8. OSGi & Services

### Component Declaration & Lifecycle

```java
@Component(
    service = MyService.class,
    immediate = true,
    configurationPolicy = ConfigurationPolicy.REQUIRE,
    property = {
        "service.ranking:Integer=100"
    },
    scope = ServiceScope.SINGLETON
)
public class MyServiceImpl implements MyService {

    @Activate
    protected void activate(MyConfig config) {
        // Khởi tạo service khi active
    }

    @Modified
    protected void modified(MyConfig config) {
        // Cập nhật cấu hình khi configMgr thay đổi
    }

    @Deactivate
    protected void deactivate() {
        // Giải phóng tài nguyên, đóng thread pool / stream
    }
}
```

### Dependency Injection (`@Reference`)

```java
// Bắt buộc (Mặc định)
@Reference
private MyService service;

// Tùy chọn (Optional & Dynamic)
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
private volatile MyOptionalService optionalService; // Bắt buộc khai báo volatile

// Đa tham chiếu (Multiple)
@Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
private volatile List<EventHandler> handlers;

// Lọc theo target property
@Reference(target = "(component.name=com.myproject.core.CachedService)")
private MyService cachedImpl;
```

### OSGi Configuration (OCD)

```java
@ObjectClassDefinition(name = "My Project Service Configuration", description = "Cấu hình API endpoints và timeout")
public @interface MyConfig {

    @AttributeDefinition(name = "Endpoint URL", description = "Base URL của backend API")
    String endpoint() default "https://api.myproject.com/v1";

    @AttributeDefinition(name = "Timeout (ms)")
    int timeout() default 5000;
}
```

Liên kết OCD vào Component:

```java
@Component(service = MyService.class)
@Designate(ocd = MyConfig.class)
public class MyServiceImpl implements MyService { ... }
```

**File `.cfg.json` cấu hình theo run mode:**

```text
ui.config/.../apps/myproject/osgiconfig/config.publish.prod/
└── com.myproject.core.services.impl.MyServiceImpl.cfg.json
```

***

## 9. Servlets & Filters

### Registration

**1. Resource-Type Based Servlet (Khuyên dùng):**

```java
@Component(service = Servlet.class)
@SlingServletResourceTypes(
    resourceTypes = "myproject/components/product-list",
    selectors     = "filter",
    extensions    = "json",
    methods       = HttpConstants.METHOD_GET
)
public class ProductListServlet extends SlingSafeMethodsServlet {
    @Override
    protected void doGet(SlingHttpServletRequest req, SlingHttpServletResponse res) throws IOException {
        res.setContentType("application/json");
        res.setCharacterEncoding("UTF-8");
        res.getWriter().write("{"status":"ok"}");
    }
}
```

**2. Path-Based Servlet:**

```java
@Component(service = Servlet.class)
@SlingServletPaths("/bin/myproject/checkout")
public class CheckoutServlet extends SlingAllMethodsServlet {
    @Override
    protected void doPost(SlingHttpServletRequest req, SlingHttpServletResponse res) {
        // Cần bổ sung rule allow trong Dispatcher filter
    }
}
```

### Request & Response APIs

```java
Resource resource = request.getResource();
ResourceResolver resolver = request.getResourceResolver(); // Không tự ý đóng resolver của request!

String[] selectors = request.getRequestPathInfo().getSelectors();
String suffix = request.getRequestPathInfo().getSuffix();
String extension = request.getRequestPathInfo().getExtension();
String query = request.getParameter("q");

response.setStatus(SlingHttpServletResponse.SC_OK);
response.setContentType("application/json;charset=UTF-8");
```

### Sling Filters

```java
@Component(service = Filter.class)
@SlingServletFilter(
    scope = SlingServletFilterScope.REQUEST,
    pattern = "/content/myproject/.*"
)
@ServiceRanking(-700) // Thứ tự thực thi (số âm chạy sau, số dương chạy trước)
public class SecurityFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
            throws IOException, ServletException {
        // Tiền xử lý
        chain.doFilter(req, res);
        // Hậu xử lý
    }
}
```

_Scopes:_ `REQUEST` · `INCLUDE` · `COMPONENT` · `FORWARD` · `ERROR`

***

## 10. Schedulers, Sling Jobs & Events

### Scheduler (Runnable)

```java
@Component(service = Runnable.class, immediate = true)
@Designate(ocd = MySchedulerConfig.class)
public class DailySyncTask implements Runnable {
    @Override
    public void run() {
        // Chạy ngầm định kỳ
    }
}
```

**Quartz Cron Syntax (Bắt đầu với GIÂY):**

```text
Giây  Phút  Giờ  Ngày-trong-tháng  Tháng  Ngày-trong-tuần  [Năm]

0 0 2 * * ?        # 2:00 sáng mỗi ngày
0 */15 * * * ?     # Mỗi 15 phút
0 0 */4 * * ?      # Mỗi 4 giờ
0 30 6 ? * MON-FRI # 6:30 sáng từ thứ Hai đến thứ Sáu
```

_(Lưu ý: Một trong hai trường ngày phải là `?`, không dùng cả hai `*`)_

### Sling Jobs (Guaranteed Execution)

```java
// Tạo Sling Job (Producer)
@Reference
private JobManager jobManager;

Map<String, Object> props = new HashMap<>();
props.put("productId", "PROD-1234");
jobManager.addJob("myproject/sync/product", props);
```

```java
// Xử lý Sling Job (Consumer)
@Component(
    service = JobConsumer.class,
    property = { JobConsumer.PROPERTY_TOPICS + "=myproject/sync/product" }
)
public class ProductSyncConsumer implements JobConsumer {
    @Override
    public JobResult process(Job job) {
        String id = job.getProperty("productId", String.class);
        // Trả về: JobResult.OK | JobResult.FAILED (cho phép retry) | JobResult.CANCEL
        return JobResult.OK;
    }
}
```

### Resource Change Listener & Event Handling

```java
@Component(
    service = ResourceChangeListener.class,
    property = {
        ResourceChangeListener.PATHS + "=/content/myproject/products",
        ResourceChangeListener.CHANGES + "=ADDED",
        ResourceChangeListener.CHANGES + "=CHANGED"
    }
)
public class ProductChangeListener implements ResourceChangeListener {
    @Override
    public void onChange(List<ResourceChange> changes) {
        // Chỉ thực hiện kiểm tra nhẹ, sau đó dispatch một Sling Job để xử lý nặng
    }
}
```

### Decision Matrix: Khi nào dùng giải pháp nào?

| Yêu cầu                                                            | Giải pháp phù hợp          | Lưu trữ                   |
| ------------------------------------------------------------------ | -------------------------- | ------------------------- |
| Chạy theo mốc thời gian cố định                                    | **Scheduler**              | In-memory                 |
| Lắng nghe thay đổi node JCR                                        | **ResourceChangeListener** | JCR event system          |
| Xử lý bất đồng bộ nặng, không được phép mất tác vụ khi restart pod | **Sling Job**              | `/var/eventing/jobs`      |
| Quy trình cần người duyệt, tương tác UI hoặc audit trail           | **Workflow**               | `/var/workflow/instances` |

***

## 11. Workflows

### Paths & Kiến trúc

- `/conf/global/settings/workflow/models/`: Chứa file định nghĩa mô hình workflow (Design time).
- `/var/workflow/models/`: Bản sao chạy thực tế (Runtime). **Bắt buộc bấm nút Sync trong Workflow Console sau khi sửa model**.
- `/conf/global/settings/workflow/launcher/config/`: Danh sách launcher kích hoạt workflow tự động.
- `/var/workflow/instances/`: Trạng thái các instance đang chạy và lịch sử thực thi.

### Custom Workflow Process Step

```java
@Component(
    service = WorkflowProcess.class,
    property = { "process.label=My Project - Custom Step" }
)
public class CustomStepProcess implements WorkflowProcess {
    @Override
    public void execute(WorkItem item, WorkflowSession session, MetaDataMap args) throws WorkflowException {
        String payload = item.getWorkflowData().getPayload().toString();
        ResourceResolver resolver = session.adaptTo(ResourceResolver.class);
        
        // Lưu data dùng chung cho các step sau
        item.getWorkflow().getWorkflowData().getMetaDataMap().put("customKey", "customValue");
    }
}
```

### Dynamic Participant Chooser

```java
@Component(
    service = ParticipantStepChooser.class,
    property = { "chooser.label=My Project - Route by Category" }
)
public class DynamicRouter implements ParticipantStepChooser {
    @Override
    public String getParticipant(WorkItem item, WorkflowSession session, MetaDataMap args) {
        return "content-approvers-group"; // Trả về user ID hoặc group ID
    }
}
```

***

## 12. Security, Users & Repoinit

### JCR Locations & Evaluation Rules

```text
/home/users/<bucket>/<userid>     # rep:User
/home/groups/<bucket>/<groupid>   # rep:Group
/home/users/system/...            # Service users (hệ thống)
<node>/rep:policy                 # Danh sách ACL (rep:GrantACE / rep:DenyACE)
```

**Thứ tự ưu tiên đánh giá quyền:**

1. **DENY BY DEFAULT** — Mặc định không có quyền gì.
2. **CLOSEST ACL WINS** — Quyền cấu hình ở node sâu hơn sẽ ghi đè quyền thừa kế từ node cha.
3. **DENY BEATS ALLOW** — Nếu ở cùng một level có cả Allow và Deny thì Deny luôn thắng.
   _(Không thể ghi đè lệnh Deny bằng cách gán user vào một group khác có quyền Allow)_.

### Repoinit Syntax

Định nghĩa service users và phân quyền an toàn qua cấu hình OSGi:

```
create service user myproject-reader with path system/cq:services/myproject

create group myproject-editors
add myproject-editors to group content-authors

set ACL for myproject-editors
    allow jcr:read,rep:write on /content/myproject
    allow jcr:read on /content/dam/myproject restriction(rep:glob,"")
    deny crx:replicate on /content/myproject/drafts
end

create path (sling:Folder) /var/myproject/data
```

### Lấy ResourceResolver từ Service User

```java
// 1. Cấu hình OSGi Service User Mapping:
// bundleSymbolicName:subserviceName=[serviceUserName]
// com.myproject.core:content-reader=[myproject-reader]

// 2. Lấy resolver trong Java code:
Map<String, Object> authInfo = Collections.singletonMap(
    ResourceResolverFactory.SUBSERVICE, "content-reader"
);

try (ResourceResolver resolver = resolverFactory.getServiceResourceResolver(authInfo)) {
    // Luôn dùng try-with-resources để tự động đóng resolver sau khi dùng xong
    Resource res = resolver.getResource("/content/myproject");
}
```

***

## 13. Content Fragments & Experience Fragments

### Content Fragments (CF)

- **Model path:** `/conf/<project>/settings/dam/cfm/models/<model>`
- **Fragment path:** `/content/dam/<project>/fragments/<fragment>`
- **Mục đích:** Quản lý nội dung thuần (dữ liệu có cấu trúc, không chứa layout) để phục vụ cho đa kênh (Headless).

**Đọc CF trong Sling Model:**

```java
Resource fragmentResource = resourceResolver.getResource(fragmentPath);
if (fragmentResource != null) {
    ContentFragment cf = fragmentResource.adaptTo(ContentFragment.class);
    if (cf != null) {
        ContentElement element = cf.getElement("title");
        String title = element.getContent();
        
        // Đọc variation cụ thể
        ContentVariation socialVar = element.getVariation("social");
    }
}
```

### Experience Fragments (XF)

- **Structure path:** `/content/experience-fragments/<project>/.../<variation>` (Lưu dưới dạng `cq:Page`).
- **Mục đích:** Chứa cả nội dung và giao diện (HTML + Clientlib), có thể nhúng trực tiếp vào các trang hoặc xuất ra kênh ngoài.
- **HTML Export:** Truy cập URL `/content/experience-fragments/.../master.plain.html` để lấy HTML thuần không có chrome vỏ bọc.

### So sánh CF vs XF

| Đặc tính           | Content Fragment (CF)                                        | Experience Fragment (XF)                                     |
| ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| **JCR Node Type**  | `dam:Asset`                                                  | `cq:Page`                                                    |
| **Vị trí lưu trữ** | `/content/dam/...`                                           | `/content/experience-fragments/...`                          |
| **Định nghĩa từ**  | Content Fragment Model                                       | Editable Template                                            |
| **Bản chất**       | Nội dung thuần không có style (Content without presentation) | Trải nghiệm hoàn chỉnh có layout (Presentation with content) |
| **Đầu ra chính**   | JSON qua GraphQL / REST API                                  | HTML component nhúng trên trang hoặc kênh ngoài              |

***

## 14. Sling Model Exporter & SPA

```java
@Model(
    adaptables = SlingHttpServletRequest.class,
    adapters = { Cta.class, ComponentExporter.class },
    resourceType = "myproject/components/cta",
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL
)
@Exporter(
    name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, // "jackson"
    extensions = ExporterConstants.SLING_MODEL_EXTENSION // "json"
)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CtaImpl implements Cta, ComponentExporter {

    @JsonProperty("linkUrl")
    public String getUrl() { ... }

    @JsonIgnore
    public String getInternalState() { ... }

    @Override
    public String getExportedType() {
        return "myproject/components/cta";
    }
}
```

- URL truy cập model JSON: `/content/myproject/en/jcr:content/root/cta.model.json`
- Vì có selector `model` và extension `.json`, Dispatcher có thể cache URL này như một file tĩnh.

***

## 15. Multi-Site Manager (MSM) & Translation

### Cấu trúc thư mục chuẩn

```tree
/content/myproject/
├── language-masters/      # Nguồn nội dung gốc do author biên soạn
│   ├── en                 # Master source
│   ├── fr                 # Language copy đã dịch từ en
│   └── de                 # Language copy đã dịch từ en
├── us/en                  # Live copy từ language-masters/en
├── ca/
│   ├── en                 # Live copy từ language-masters/en
│   └── fr                 # Live copy từ language-masters/fr
└── de/de                  # Live copy từ language-masters/de
```

### Hủy kế thừa (Cancellation)

- **Property Level:** `cq:propertyInheritanceCancelled = ["title", "heroImage"]` _(Khuyên dùng để chỉ ngắt kế thừa field cụ thể)_.
- **Page Level:** Suspend (tạm dừng đồng bộ, có thể resume) hoặc Detach (ngắt vĩnh viễn thành page độc lập).

### Quy trình dịch thuật và triển khai (3 bước)

1. **TRANSLATE**: Dịch từ source language vào language master tương ứng.
2. **ROLL OUT**: Đồng bộ từ language master ra các live copy của từng quốc gia/khu vực (chỉ diễn ra trên Author).
3. **ACTIVATE**: Publish trang đã cập nhật lên môi trường Publish.

***

## 16. Replication & Cloud Distribution

### Classic Replication (AEM 6.5)

- **Replication Agents:** Nằm tại `/etc/replication/agents.author/publish`.
- **Agent User:** Đọc nội dung trên Author để đóng gói (nếu thiếu quyền sẽ bỏ qua âm thầm).
- **Transport User:** Gửi request và ghi nội dung vào Publish (nếu sai password/quyền sẽ gây HTTP 401 và nghẽn toàn bộ replication queue).

### AEM as a Cloud Service (Sling Content Distribution)

- Sử dụng mô hình **Pub/Sub (Publish/Subscribe)**.
- Author đẩy nội dung vào một pipeline trung gian. Các pod Publish tự động subscribe và pull dữ liệu về.
- Khi một pod Publish mới được sinh ra do auto-scale, pod đó sẽ tự động đồng bộ dữ liệu mà không cần cấu hình thêm replication agent.

### The 6-Step Replication Troubleshooting Walk

:::tip\[Quy trình kiểm tra khi nội dung không xuất hiện trên Publish]

1. **Was it ACTIVATED?** — Kiểm tra replication status trong Page Properties.
2. **Is the QUEUE blocked?** — Kiểm tra xem có item nào bị lỗi làm nghẽn hàng đợi phía sau không.
3. **Node on PUBLISH?** — Mở cửa sổ ẩn danh truy cập trực tiếp port Publish để loại trừ lỗi Dispatcher.
4. **REFERENCES published?** — Các Content Fragment, Experience Fragment, hoặc Asset nhúng đã được kích hoạt chưa?
5. **DISPATCHER cached?** — Kiểm tra xem cache flush request có được gửi thành công đến Dispatcher không.
6. **CDN cached?** — Kiểm tra tầng cache ngoài cùng trên CDN / Cloudflare.
   :::

***

## 17. AEM as a Cloud Service (AEMaaCS)

### So sánh kiến trúc chính

| Tiêu chí                      | AEM 6.5 (On-Prem / AMS)                | AEM as a Cloud Service                                                 |
| ----------------------------- | -------------------------------------- | ---------------------------------------------------------------------- |
| **Hệ thống**                  | Server cố định, tự quản lý nâng cấp    | Dịch vụ đám mây, tự động cập nhật liên tục                             |
| **Khả năng mở rộng**          | Provisioning thủ công cho giờ cao điểm | Tự động auto-scale số lượng pod Publish theo tải                       |
| **Thư mục `/apps` & `/libs`** | Cho phép ghi tại runtime               | **Read-only / Bất biến (Immutable)** tại runtime                       |
| **Cấu hình ACL / Users**      | Tạo thủ công qua UI hoặc package       | Bắt buộc định nghĩa qua **Repoinit**                                   |
| **NodeStore**                 | Thường dùng TarMK hoặc MongoMK         | Author: **DocumentNodeStore (MongoDB)**; Publish: **SegmentNodeStore** |
| **Hotfix khẩn cấp**           | Có thể patch thẳng code / bundle       | **Không có đường tắt hotfix**, mọi thay đổi đều phải qua pipeline Git  |

### Cloud Deployment Pipelines

1. **Full-stack Pipeline:** Build và deploy toàn bộ backend Java, frontend, và repository package.
2. **Front-end Pipeline:** Build và deploy nhanh các tài nguyên frontend (JS/CSS) mà không cần build lại toàn bộ Java.
3. **Web-tier Pipeline:** Deploy cấu hình Apache và Dispatcher riêng biệt (rất nhanh).
4. **Config Pipeline:** Áp dụng nhanh các cấu hình hệ thống cụ thể.

***

## 18. Cross-Topic Architectural Rules

- **"Works on author, blank on publish"**: Nguyên nhân phổ biến nhất là Author đã authenticated còn Publish là anonymous. Code phụ thuộc vào session privilege mà anonymous không có, hoặc reference content (CF, XF, Asset) chưa từng được publish.
- **"A dot is cached, a question mark is not"**: Selectors và extensions (`.model.json`, `.filter.html`) thuộc về URL path nên Dispatcher cache được dễ dàng. Query parameters (`?page=2`) mặc định bỏ qua cache.
- **Derived content cần invalidation riêng**: Khi một trang nhúng Experience Fragment hoặc Content Fragment, việc activate Fragment nguồn không tự động flush cache của mọi trang đang nhúng nó.
- **Deny beats allow, closest ACL wins**: Không thể bypass một quyền Deny bằng cách gán thêm group khác.
- **Không có đường tắt trên Cloud**: Mọi thay đổi đều phải đi qua Git và Cloud Manager Pipeline. Hãy tận dụng Feature Flags cho việc rollout tính năng an toàn.
