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/aem-cheatsheet.md.

AEM developer quick reference cheatsheet covering common tasks, APIs, annotations, and commands.

AEM Cheat Sheet

1. Quick URLs & Operations

Console URLs

URLDescription
/system/console/bundlesBundle list and states (Active, Resolved, Installed)
/system/console/componentsOSGi Declarative Services — Active or Unsatisfied
/system/console/configMgrOSGi configuration in effect
/system/console/servicesThe OSGi service registry and service rankings
/system/console/servletresolverWhich servlet handles a given URL
/system/console/status-adaptersRegistered Sling adapter factories
/system/console/slinglogLogger configuration and log levels
/system/console/tracerSling Log Tracer for request-level tracing
/system/console/depfinderWhich bundle exports or imports a package
/system/console/jmxMBeans — Oak, replication queues, index stats
/system/console/healthcheckSystem health checks
/system/console/slingeventSling job queues, topics, and failure counters
/system/console/status-ThreadsThread dump
/system/console/status-slingsettingsActive Sling run modes and instance settings
/crx/deCRXDE Lite repository browser
/crx/packmgrPackage Manager
/etc/replication/agents.authorReplication agents (AEM 6.5)
/libs/cq/search/content/querydebug.htmlQueryBuilder debugger
/libs/granite/operations/content/diagnosistools/queryPerformance.htmlQuery performance and Oak index analysis
/useradminAEM Security & User Admin console
/myworkflowsAEM Workflow console

Log Locations

LocationDescription
crx-quickstart/logs/error.logApplication errors, stack traces, and OSGi bundle messages
crx-quickstart/logs/request.logRequest execution time and response codes
crx-quickstart/logs/access.logHTTP request source IP, timestamp, and HTTP method
crx-quickstart/logs/stdout.logJVM standard out and startup console messages
<apache>/logs/dispatcher.logDispatcher cache hit/miss, filter rules, and flush requests
Cloud ServiceCloud Manager → Download Logs, hoặc aio aem:rde:logs

URL Debug Parameters

ParameterDescription
?wcmmode=(edit|preview|design|disabled)Đặt WcmMode để test hiển thị component theo từng mode
?debugClientLibs=trueXuất toàn bộ clientlib categories thành từng file JS/CSS riêng lẻ chưa minified
?debugClientLibs=true&debugClientLibsAppend=trueGộp tất cả JS/CSS vào 1 file duy nhất để debug
?debug=messagesHiể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
[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)

# 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:

# 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

CommandDescription
mvn clean install -PautoInstallSinglePackageDeploy package đầy đủ lên Author (port 4502)
mvn clean install -PautoInstallSinglePackagePublishDeploy package đầy đủ lên Publish (port 4503)
mvn clean install -PautoInstallBundle -pl coreChỉ build và deploy bundle OSGi core (rất nhanh)
mvn clean install -PautoInstallPackage -pl ui.appsChỉ build và deploy package ui.apps

FileVault (vlt) & filter.xml

# 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:

<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

PropertyDescription
sling:resourceTypeTrỏ đến path của component render node này (ví dụ: myproject/components/page)
sling:resourceSuperTypeKế thừa logic render và dialog từ component cha
cq:templatePath template được dùng để khởi tạo page này
jcr:primaryTypeJCR node type (cq:Page, cq:PageContent, nt:unstructured, cq:Component, ...)
jcr:titleTiêu đề của page hoặc component
cq:allowedTemplatesRegex 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:configRefTrỏ một content tree đến thư mục cấu hình /conf tương ứng
allowProxyCho phép truy cập clientlib dưới /apps thông qua /etc.clientlibs/
categoriesĐịnh danh clientlib category để include trong HTL
embedGộ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:

<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

/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

PropertyDescription
jcr:primaryType="cq:Component"Đánh dấu node là một AEM Component
jcr:titleTên hiển thị của component trong Sidekick / Component Browser
jcr:descriptionTooltip giải thích mục đích component
componentGroupTên group phân loại. Đặt .hidden nếu muốn ẩn khỏi author
sling:resourceSuperTypeKế 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)

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:

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

(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:

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

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

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

@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:

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

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

<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

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

StatementPurpose
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-unwrapBỏ 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):

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

${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

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

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

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

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

Proxy Path Translation

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

<!-- 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:

/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

/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

@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

AnnotationDescription
@ValueMapValueInject property từ chính node hiện tại
@ChildResourceInject child node thành Resource, một Model con, hoặc List<Model>
@ResourcePathResolve path lưu trong property thành một Resource
@OSGiServiceInject một OSGi service vào model
@ScriptVariableInject các HTL binding objects (currentPage, currentStyle, resourceResolver) — yêu cầu adaptable là SlingHttpServletRequest
@RequestAttributeInject attribute được set từ filter hoặc servlet cấp cao hơn
@SlingObjectInject ResourceResolver, Resource, SlingHttpServletRequest, SlingHttpServletResponse
@SelfInject chính object adaptable (Resource hoặc Request)

Modifiers & Lifecycle

@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

<!-- HTL -->
<sly data-sly-use.model="com.myproject.core.models.CtaModel"/>
// Java adaptation
CtaModel model = resource.adaptTo(CtaModel.class);
if (model != null) {
    // Logic xử lý
}
// 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

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

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

@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:

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

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

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

@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:

@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

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

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

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

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)

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

@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ầuGiải pháp phù hợpLưu trữ
Chạy theo mốc thời gian cố địnhSchedulerIn-memory
Lắng nghe thay đổi node JCRResourceChangeListenerJCR event system
Xử lý bất đồng bộ nặng, không được phép mất tác vụ khi restart podSling Job/var/eventing/jobs
Quy trình cần người duyệt, tương tác UI hoặc audit trailWorkflow/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

@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

@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

/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

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

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ínhContent Fragment (CF)Experience Fragment (XF)
JCR Node Typedam:Assetcq:Page
Vị trí lưu trữ/content/dam/.../content/experience-fragments/...
Định nghĩa từContent Fragment ModelEditable Template
Bản chấtNộ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ínhJSON qua GraphQL / REST APIHTML component nhúng trên trang hoặc kênh ngoài

14. Sling Model Exporter & SPA

@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

/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ốngServer cố định, tự quản lý nâng cấpDịch vụ đám mây, tự động cập nhật liên tục
Khả năng mở rộngProvisioning thủ công cho giờ cao điểmTự động auto-scale số lượng pod Publish theo tải
Thư mục /apps & /libsCho phép ghi tại runtimeRead-only / Bất biến (Immutable) tại runtime
Cấu hình ACL / UsersTạo thủ công qua UI hoặc packageBắt buộc định nghĩa qua Repoinit
NodeStoreThường dùng TarMK hoặc MongoMKAuthor: DocumentNodeStore (MongoDB); Publish: SegmentNodeStore
Hotfix khẩn cấpCó thể patch thẳng code / bundleKhô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.