AEM Cheat Sheet
1. Quick URLs & Operations
Console URLs
Log Locations
URL Debug Parameters
[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:
crx-quickstart/conf/sling.properties → sling.run.modes=author,dev
- Tham số dòng lệnh:
java -jar cq-author-p4502.jar -r dev
- 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
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
David's Model (7 nguyên tắc thiết kế JCR)
- 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.
- 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.
- 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).
- 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ì.
- 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ể.
- Files are Files are Files. — Lưu binary/tệp tin với đúng node type
nt:file và nt:resource.
- 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):
print/a4.html — Khớp cả 2 selectors print và a4
print.html — Khớp selector đầu tiên print
a4.html — Không hợp lệ nếu thiếu selector print đứng trước
sample.html — Khớp tên base của component (sling:resourceType = .../sample)
html.html — Fallback theo extension
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>
Component .content.xml Properties
Dialog Skeleton (Granite Coral UI)
cq:dialog (sling:resourceType="cq/gui/components/authoring/dialog") └ content → granite/ui/components/coral/foundation/container └ tabs → granite/ui/components/coral/foundation/tabs └ mainTab (jcr:title="Properties") → .../foundation/container └ columns → .../foundation/fixedcolumns └ column → .../foundation/container
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
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
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 /conf/<project>/settings/wcm/policies /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:
cq:allowedTemplates trên page cha hoặc site root (Độ ưu tiên cao nhất).
allowedPaths trên template definition.
allowedParents trên template.
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]
- Template đã chuyển sang trạng thái
enabled chưa?
allowedPaths regex có khớp với đường dẫn page đang tạo không?
- Thuộc tính
cq:allowedTemplates trên content root folder có bao gồm template này không?
- 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]
- Component đã được tích chọn trong
components list của container policy chưa?
componentGroup của component có bị rỗng hoặc là .hidden không?
- Layout container con có bị thiếu ánh xạ
cq:policy không?
- 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
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?
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:
- DENY BY DEFAULT — Mặc định không có quyền gì.
- CLOSEST ACL WINS — Quyền cấu hình ở node sâu hơn sẽ ghi đè quyền thừa kế từ node cha.
- 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
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
us/en # Live copy từ language-masters/en 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)
- TRANSLATE: Dịch từ source language vào language master tương ứng.
- 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).
- 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]
- Was it ACTIVATED? — Kiểm tra replication status trong Page Properties.
- 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.
- Node on PUBLISH? — Mở cửa sổ ẩn danh truy cập trực tiếp port Publish để loại trừ lỗi Dispatcher.
- REFERENCES published? — Các Content Fragment, Experience Fragment, hoặc Asset nhúng đã được kích hoạt chưa?
- DISPATCHER cached? — Kiểm tra xem cache flush request có được gửi thành công đến Dispatcher không.
- 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
Cloud Deployment Pipelines
- Full-stack Pipeline: Build và deploy toàn bộ backend Java, frontend, và repository package.
- 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.
- Web-tier Pipeline: Deploy cấu hình Apache và Dispatcher riêng biệt (rất nhanh).
- 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.