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

# MSM (Multi Site Manager) trong AEM 6.5

## 1. MSM giải quyết bài toán gì?

MSM cho phép **một nội dung nguồn (source/master) được tái sử dụng ở nhiều site đích (target)** mà vẫn kiểm soát được việc đồng bộ. Hai use case chính:

| Use case                       | Cơ chế                                                 | Ví dụ                                                    |
| ------------------------------ | ------------------------------------------------------ | -------------------------------------------------------- |
| **Multi-brand / multi-region** | Blueprint → Live Copy                                  | Site global → site theo quốc gia                         |
| **Đa ngôn ngữ**                | Language Master → Language Copy (tích hợp Translation) | `/content/site/language-masters/en` → `/content/site/vi` |

**Điểm cốt lõi hay bị hiểu sai: MSM là cơ chế COPY-based, không phải runtime inheritance.**
Nội dung của live copy được _ghi vật lý_ vào JCR của chính nó khi rollout. Runtime không resolve ngược về source. Điều này khác với Sling Resource Merger, Context-Aware Configuration hay Editable Templates (policy inheritance).

***

## 2. Kiến trúc và các khái niệm

```mermaid
flowchart LR
    A[Blueprint<br/>Cấu hình] --> B[Source/Master<br/>/content/brand/global/en]
    B -->|Rollout<br/>Rollout Config + Trigger| C[Live Copy<br/>/content/brand/vn/en]
    C <-->|Live Relationship<br/>Mỗi node| D[Source/Master]
```

- **Source / Master:** trang gốc, không cần cấu hình gì đặc biệt.
- **Blueprint:** cấu hình giúp tạo nhiều live copy từ một source theo kiểu "template", và cho phép rollout từ source ra **tất cả** live copy dựa trên blueprint. Một live copy **không bắt buộc** phải tạo từ blueprint, bạn vẫn có thể tạo live copy trực tiếp từ một trang.
- **Live Copy:** bản sao có quan hệ với source.
- **Live Relationship:** quan hệ ở mức **từng resource** (page, component, property), không chỉ ở root.
- **Rollout Config:** tập hợp **Live Action** kèm **trigger**.
- **Live Action:** đơn vị logic thực thi khi rollout (copy, update, delete, ...).
- **Live Status:** trạng thái đồng bộ (lần rollout cuối, bị suspend hay không, ...).

***

## 3. JCR properties: phần trọng tâm

### 3.1 Live Copy root: `cq:LiveSyncConfig`

```tree
/content/brand/vn/en
  └── jcr:content                         (mixin: cq:LiveSync)
        ├── cq:lastRolledout   : Date
        ├── cq:lastRolledoutBy : String
        └── cq:LiveSyncConfig             (nodeType: cq:LiveCopy)
              ├── cq:master          : String  → /content/brand/global/en
              ├── cq:isDeep          : Boolean → rollout cả cây con
              └── cq:rolloutConfigs  : String[] → /libs/msm/wcm/rolloutconfigs/default
```

| Property                                  | Ý nghĩa                                | Lưu ý thi/thực tế                                                                               |
| ----------------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `cq:master`                               | Đường dẫn source                       | Sai path hoặc source bị xoá thì relationship hỏng (`isSourceExisting() == false`)               |
| `cq:isDeep`                               | `true` thì áp dụng cho toàn bộ cây con | Nếu `false`, chỉ trang root được rollout. Đây là lỗi "con không cập nhật" rất phổ biến          |
| `cq:rolloutConfigs`                       | Mảng path tới rollout config           | **Có thể gán nhiều config**, thứ tự có ý nghĩa. Trigger của từng config quyết định khi nào chạy |
| `cq:lastRolledout` / `cq:lastRolledoutBy` | Audit lần rollout cuối                 | Nằm ở `jcr:content` của live copy (mixin `cq:LiveSync`)                                         |

Mixin `cq:LiveSync` gắn trên `jcr:content` của **từng trang** thuộc live copy, đánh dấu nó có quan hệ sync.

### 3.2 Suspend (Cancel inheritance): `cq:LiveSyncCancelled`

```tree
jcr:content                         (mixin: cq:LiveSyncCancelled)
  └── cq:isCancelledForChildren : Boolean
```

- Gắn mixin `cq:LiveSyncCancelled` thì node đó **bị loại khỏi rollout** (suspend).
- `cq:isCancelledForChildren = true` thì suspend **cả cây con** (tương ứng "Suspend with children").
- Resume (re-enable) sẽ gỡ mixin và property này.
- Áp dụng được ở **page**, **component** (node con của `jcr:content`) hoặc node bất kỳ có relationship.

### 3.3 Cancel ở mức property: `cq:propertyInheritanceCancelled`

```tree
jcr:content/root/container/title    (mixin: cq:PropertyLiveSyncCancelled)
  ├── jcr:title : "Tiêu đề riêng của VN"
  └── cq:propertyInheritanceCancelled : String[] = ["jcr:title"]
```

- Property nào nằm trong mảng này sẽ **không bị ghi đè** bởi `contentUpdate`.
- Trong Touch UI, đây là biểu tượng **khoá/mở khoá** trên Page Properties và từng component dialog. Khi mở khoá, AEM thêm mixin và property trên.
- **Đây là cơ chế duy nhất cho phép "local override" mà vẫn giữ quan hệ sync cho phần còn lại.**

### 3.4 Rollout Config: `cq:RolloutConfig`

Vị trí thường gặp trong 6.5:

- `/libs/msm/wcm/rolloutconfigs/`: **OOTB, không sửa**
- `/apps/msm/rolloutconfigs/`: **cấu hình custom** của project

```tree
/apps/msm/rolloutconfigs/my-config   (cq:RolloutConfig)
  ├── jcr:title / jcr:description
  └── jcr:content
        ├── cq:trigger : String       ("rollout" | "modification" | "publish" | "deactivate")
        ├── contentCopy      (cq:LiveSyncAction) name="contentCopy"
        ├── contentUpdate    (cq:LiveSyncAction) name="contentUpdate"
        ├── contentDelete    (cq:LiveSyncAction) name="contentDelete"
        └── referencesUpdate (cq:LiveSyncAction) name="referencesUpdate"
```

**Các trigger (hay ra thi):**

| Trigger        | Khi nào chạy                                |
| -------------- | ------------------------------------------- |
| `rollout`      | Khi người dùng/API gọi rollout thủ công     |
| `modification` | Ngay khi source được sửa ("Push on modify") |
| `publish`      | Khi source được activate                    |
| `deactivate`   | Khi source bị deactivate                    |

> Nếu live copy gắn một config có trigger `modification`, nó **không** rollout thủ công trừ khi config đó cũng có trigger `rollout`. Hãy kiểm tra trigger khi "rollout không chạy gì".

**Các Live Action OOTB quan trọng:**

| Action             | Chức năng                                                         |
| ------------------ | ----------------------------------------------------------------- |
| `contentCopy`      | Tạo node/trang mới ở target nếu chưa có (copy từ source)          |
| `contentUpdate`    | Cập nhật property/node đã tồn tại, **bỏ qua** property bị cancel  |
| `contentDelete`    | Xoá ở target những node đã bị xoá ở source                        |
| `editProperties`   | Đồng bộ một số property chỉ định trong rollout config             |
| `orderChildren`    | Giữ thứ tự node con giống source                                  |
| `referencesUpdate` | Viết lại reference trỏ vào cây source thành trỏ vào cây live copy |
| `productUpdate`    | Dành cho commerce                                                 |
| `workflow`         | Khởi chạy workflow trên target (tham số là model path)            |
| `pageMove`         | Xử lý khi trang source bị move                                    |

Các property mà `contentCopy`/`contentUpdate` bỏ qua (ví dụ `jcr:uuid`, các property hệ thống) được cấu hình trong OSGi factory của action tương ứng, qua `cq.wcm.msm.action.excludedprops`, `cq.wcm.msm.action.excludednodetypes` và `cq.wcm.msm.action.excludedparagraphproperties`. Kiểm tra tên chính xác trong Web Console của instance bạn.

***

## 4. Luồng rollout bên trong

```mermaid
flowchart TD
    A[Người dùng/code gọi<br/>RolloutManager.rollout] --> B[LiveRelationshipManager<br/>xác định LiveRelationship]
    B --> C[Kiểm tra LiveStatus<br/>từng relationship]
    C --> D{Cancel/Target tồn tại?}
    D -->|Bị cancel| E[Bỏ qua node này]
    D -->|OK| F[Chạy Live Actions<br/>theo thứ tự config]
    F --> G[Tôn trọng cq:LiveSyncCancelled<br/>và cq:propertyInheritanceCancelled]
    G --> H[Cập nhật cq:lastRolledout<br/>và cq:lastRolledoutBy]
    H --> I[Commit theo batch]
```

Các bước chính:

1. Người dùng (hoặc code) gọi `RolloutManager.rollout(...)`.
2. `LiveRelationshipManager` xác định các `LiveRelationship` từ source đến từng target.
3. Với mỗi relationship, AEM kiểm tra `LiveStatus` (có bị cancel không, target tồn tại không, ...).
4. Chạy tuần tự các Live Action theo thứ tự cấu hình trong rollout config, tôn trọng `cq:LiveSyncCancelled` và `cq:propertyInheritanceCancelled`.
5. Cập nhật `cq:lastRolledout` và `cq:lastRolledoutBy`, rồi commit theo batch.

OSGi config `com.day.cq.wcm.msm.impl.RolloutManagerImpl` có các tham số về thread pool, kích thước commit batch và conflict handling. Cần tune khi rollout cây lớn.

***

## 5. Coding với MSM API

Package chính: `com.day.cq.wcm.msm.api`. Các class đáng nhớ là `RolloutManager`, `LiveRelationshipManager`, `LiveRelationship`, `LiveStatus`, `LiveAction`, `LiveActionFactory`, `RolloutConfigManager`, `MSMNameConstants`.

### 5.1 Rollout bằng code

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

    @Reference
    private RolloutManager rolloutManager;

    public void rolloutDeep(ResourceResolver resolver, String sourcePath) throws WCMException {
        PageManager pm = resolver.adaptTo(PageManager.class);
        Page source = pm.getPage(sourcePath);

        RolloutManager.RolloutParams params = new RolloutManager.RolloutParams();
        params.master  = source;
        params.isDeep  = true;                              // rollout cả cây con
        params.reset   = false;                             // true sẽ reset cả phần đã cancel inheritance
        params.trigger = RolloutManager.Trigger.ROLLOUT;
        params.delete  = false;
        // params.targets = new String[]{"/content/brand/vn/en"}; // giới hạn target (tuỳ chọn)

        rolloutManager.rollout(params);
        // RolloutManager commit theo batch; nếu cần chắc chắn, kiểm tra resolver.hasChanges()
    }
}
```

> `reset = true` là hành động **phá huỷ** local override. Tuyệt đối không bật mặc định.

### 5.2 Đọc Live Relationship và Live Status

```java
@Reference
private LiveRelationshipManager relationshipManager;

public void inspect(ResourceResolver resolver, String targetPath) throws WCMException {
    Resource target = resolver.getResource(targetPath);

    if (relationshipManager.hasLiveRelationship(target)) {
        LiveRelationship rel = relationshipManager.getLiveRelationship(target, false);
        LiveStatus status = rel.getStatus();

        String source = rel.getSourcePath();
        Calendar last = status.getLastRolledOut();
        boolean suspended = status.isCancelled();
        boolean suspendedChildren = status.isCancelledForChildren();
        boolean sourceMissing = !status.isSourceExisting();
        // ...
    }
}
```

Để lấy mọi live copy của một source: `relationshipManager.getLiveRelationships(sourceResource, null, null)` (path và filter tuỳ chọn). Dùng `isSource(resource)` để biết resource có phải source hay không.

### 5.3 Suspend / Resume / Detach bằng code

```java
LiveRelationship rel = relationshipManager.getLiveRelationship(target, false);

// Suspend (kèm con)
relationshipManager.cancelRelationship(resolver, rel, true, true);

// Resume
relationshipManager.reenableRelationship(resolver, rel, true, true);

// Detach hẳn (gỡ mixin và config, thành trang độc lập)
relationshipManager.endRelationship(resolver, rel, true, true);
```

Tham số boolean thường là `deep` và `autoSave`. Kiểm tra chữ ký chính xác trong API của bạn.

### 5.4 Custom Live Action

Cần hai phần: **LiveAction** và **LiveActionFactory** (đăng ký OSGi, tên khớp với `name` trong rollout config).

```java
@Component(
    service = LiveActionFactory.class,
    property = { LiveActionFactory.LIVE_ACTION_NAME + "=myTagSync" }
)
public class MyTagSyncActionFactory implements LiveActionFactory<LiveAction> {

    @Override
    public LiveAction createAction(Resource config) {
        ValueMap vm = config.getValueMap();
        String param = vm.get("parameters", String.class);
        return new MyTagSyncAction(param);
    }

    @Override
    public String createsAction() {
        return "myTagSync";
    }

    private static class MyTagSyncAction implements LiveAction {
        private final String param;
        MyTagSyncAction(String param) { this.param = param; }

        @Override public String getName() { return "myTagSync"; }
        @Override public int getRank() { return 100; }          // xếp sau các action OOTB
        @Override public String getParameterName() { return null; }
        @Override public String getParameters() { return param; }

        @Override
        public void execute(Resource source, Resource target, LiveRelationship rel,
                            boolean autoSave, boolean isResetRollout) throws WCMException {
            if (target == null || rel.getStatus().isCancelled()) return; // luôn tôn trọng cancel

            ModifiableValueMap target_vm = target.adaptTo(ModifiableValueMap.class);
            ValueMap src_vm = source.getValueMap();
            if (target_vm != null && src_vm.containsKey("cq:tags")) {
                target_vm.put("cq:tags", src_vm.get("cq:tags", String[].class));
            }
            // autoSave = true thì tự commit; false thì để RolloutManager commit theo batch
        }

        // Các overload khác có thể tồn tại/deprecated tuỳ phiên bản: đối chiếu với API 6.5
    }
}
```

Gắn vào rollout config bằng node `cq:LiveSyncAction` với `name=myTagSync`. Phần `parameters` được cấp lại cho factory qua `config`.

***

## 6. Best practice

1. **Dùng `/apps/msm/rolloutconfigs` cho config custom**, không sửa `/libs`.
2. **Tách rollout config theo mục đích**, ví dụ "Standard", "Push on modify" cho nội dung tin tức, "Activate on publish" cho promotion. Đừng nhồi mọi trigger vào một config.
3. **Thiết kế component sao cho cấu trúc node ổn định**: rename/move node giữa các version component làm relationship lệch, dẫn tới trùng lặp hoặc xoá nhầm.
4. **Đặt `cq:isDeep=true` có chủ đích** và test cả cây sâu.
5. **Cho phép local override qua property-level cancel** thay vì suspend cả trang.
6. **Rollout bằng service user** với ACL tối thiểu (không dùng `admin` resolver).
7. **Rollout lớn thì chia batch hoặc chạy ngoài giờ**, tune thread pool/commit size trong `RolloutManagerImpl`.
8. **Với Language Copy, tách quy trình translation khỏi rollout.** Rollout đẩy nội dung gốc, translation workflow xử lý ngôn ngữ.
9. **Flush Dispatcher có chủ đích** sau rollout. Rollout bản thân không tự activate target, trừ khi bạn cấu hình trigger/workflow.
10. **Viết integration test** cho custom Live Action (dùng AEM Mocks, kiểm tra các nhánh cancel, target null, reset).

***

## 7. Pitfalls thường gặp

| Pitfall                                     | Nguyên nhân                                                                 | Xử lý                                                                       |
| ------------------------------------------- | --------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| Con không được rollout                      | `cq:isDeep=false` hoặc node cha có `cq:isCancelledForChildren=true`         | Kiểm tra property trên `LiveSyncConfig` và các mixin `cq:LiveSyncCancelled` |
| Rollout "không chạy gì"                     | Rollout config chỉ có trigger `modification`/`publish`                      | Thêm trigger `rollout` hoặc dùng đúng cách kích hoạt                        |
| Mất nội dung local                          | Dùng `reset=true`, hoặc property chưa bị cancel ở mức property              | Dùng `cq:propertyInheritanceCancelled`, tránh reset                         |
| Trang trùng tên, bị đổi thành `*_msm_moved` | Conflict handling khi live copy đã có trang cùng tên với trang mới ở source | Chuẩn hoá naming, đọc kỹ cảnh báo sau rollout                               |
| Reference trỏ sai site                      | Thiếu `referencesUpdate`                                                    | Thêm action vào rollout config                                              |
| Source bị xoá/move                          | Relationship hỏng                                                           | Dùng `pageMove` + kiểm tra `isSourceExisting()`                             |
| Rollout chậm/timeout                        | Cây quá lớn, commit lớn                                                     | Rollout theo nhánh, tune batch/thread pool                                  |
| Custom action xoá nhầm                      | Không kiểm tra cancel/target null                                           | Luôn kiểm tra `getStatus().isCancelled()`                                   |
| Dispatcher hiển thị bản cũ                  | Chưa activate/flush target                                                  | Thêm bước publish và flush có kiểm soát                                     |
| Nhầm MSM với inheritance runtime            | Hiểu sai là copy-based                                                      | Nhớ rằng nội dung đã được **ghi** vào live copy                             |

***

## 8. Checklist

**Phải nắm chắc:**

- [ ] Blueprint vs Live Copy vs Language Copy vs Source
- [ ] Rollout vs Synchronize vs Reset vs Suspend vs Resume vs Detach
- [ ] Các trigger: `rollout`, `modification`, `publish`, `deactivate`
- [ ] Live Actions OOTB và thứ tự thực thi
- [ ] Cấu trúc JCR: `cq:LiveSyncConfig` (`cq:master`, `cq:isDeep`, `cq:rolloutConfigs`), mixin `cq:LiveSync`, `cq:LiveSyncCancelled`, `cq:propertyInheritanceCancelled`
- [ ] `cq:isCancelledForChildren` làm gì
- [ ] Vị trí rollout config: `/libs/msm/wcm/rolloutconfigs` (OOTB) và `/apps/msm/rolloutconfigs` (custom)
- [ ] API: `RolloutManager`, `LiveRelationshipManager`, `LiveRelationship`, `LiveStatus`, `LiveAction`, `LiveActionFactory`
- [ ] Cách viết và đăng ký custom Live Action (OSGi factory, `name` khớp với rollout config)
- [ ] MSM là copy-based, không phải runtime inheritance
- [ ] Quyền cần thiết để tạo live copy, rollout, suspend

**Câu hỏi hay gặp dạng tình huống:**

- _"Trang con không nhận nội dung mới sau rollout"_ → kiểm tra `cq:isDeep`, mixin `cq:LiveSyncCancelled`, trigger.
- _"Author live copy muốn giữ title riêng nhưng vẫn nhận phần còn lại"_ → cancel inheritance ở mức **property**.
- _"Cần tự động đồng bộ khi author sửa source"_ → rollout config trigger `modification`.
- _"Cần thêm logic custom khi rollout"_ → custom `LiveAction` + `LiveActionFactory`.

***

## 9. Kết luận

Với MSM, nếu nắm được ba lớp JCR (**`cq:LiveSyncConfig`** ở root, **mixin cancel** ở page/component, **`cq:propertyInheritanceCancelled`** ở property) cùng **rollout config + trigger + live action**, bạn gần như giải thích được mọi hành vi và debug được phần lớn sự cố. Khi gặp lỗi rollout, hãy đi theo thứ tự: **config và trigger → `cq:isDeep` → cancel mixin → property-level cancel → trạng thái relationship → log của RolloutManager**.
