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, and this page is available as Markdown at https://nhanbe.id.vn/aem/articles/msm/basic-msm-in-aem.md.

MSM cho phép nội dung nguồn được tái sử dụng ở nhiều site đích với cơ chế COPY-based. Hướng dẫn chi tiết về Blueprint, Live Copy, Rollout Config, Live Action và API.

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 caseCơ chếVí dụ
Multi-brand / multi-regionBlueprint → Live CopySite 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

  • 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

/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ĩaLưu ý thi/thực tế
cq:masterĐường dẫn sourceSai path hoặc source bị xoá thì relationship hỏng (isSourceExisting() == false)
cq:isDeeptrue thì áp dụng cho toàn bộ cây conNếu false, chỉ trang root được rollout. Đây là lỗi "con không cập nhật" rất phổ biến
cq:rolloutConfigsMảng path tới rollout configCó 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:lastRolledoutByAudit lần rollout cuốiNằ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

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

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
/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):

TriggerKhi nào chạy
rolloutKhi người dùng/API gọi rollout thủ công
modificationNgay khi source được sửa ("Push on modify")
publishKhi source được activate
deactivateKhi 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:

ActionChức năng
contentCopyTạo node/trang mới ở target nếu chưa có (copy từ source)
contentUpdateCập nhật property/node đã tồn tại, bỏ qua property bị cancel
contentDeleteXoá ở target những node đã bị xoá ở source
editPropertiesĐồng bộ một số property chỉ định trong rollout config
orderChildrenGiữ thứ tự node con giống source
referencesUpdateViết lại reference trỏ vào cây source thành trỏ vào cây live copy
productUpdateDành cho commerce
workflowKhởi chạy workflow trên target (tham số là model path)
pageMoveXử 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

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

@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

@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

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

@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

PitfallNguyên nhânXử lý
Con không được rolloutcq:isDeep=false hoặc node cha có cq:isCancelledForChildren=trueKiểm tra property trên LiveSyncConfig và các mixin cq:LiveSyncCancelled
Rollout "không chạy gì"Rollout config chỉ có trigger modification/publishThêm trigger rollout hoặc dùng đúng cách kích hoạt
Mất nội dung localDùng reset=true, hoặc property chưa bị cancel ở mức propertyDùng cq:propertyInheritanceCancelled, tránh reset
Trang trùng tên, bị đổi thành *_msm_movedConflict handling khi live copy đã có trang cùng tên với trang mới ở sourceChuẩn hoá naming, đọc kỹ cảnh báo sau rollout
Reference trỏ sai siteThiếu referencesUpdateThêm action vào rollout config
Source bị xoá/moveRelationship hỏngDùng pageMove + kiểm tra isSourceExisting()
Rollout chậm/timeoutCây quá lớn, commit lớnRollout theo nhánh, tune batch/thread pool
Custom action xoá nhầmKhông kiểm tra cancel/target nullLuôn kiểm tra getStatus().isCancelled()
Dispatcher hiển thị bản cũChưa activate/flush targetThêm bước publish và flush có kiểm soát
Nhầm MSM với inheritance runtimeHiểu sai là copy-basedNhớ 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.