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

# Groovy Console Snippets

## Groovy Console Snippets for AEM Development

> All scripts below are **read-only** — no mutations to the repository. Run via [AEM Groovy Console](http://localhost:4502/groovyconsole) (requires `ACS AEM Commons` or standalone `AEM Groovy Console` bundle).

***

## Find Pages Containing a Component

### By resource type — which pages use a component?

```groovy
// ✅ USE CASE: "Where is /apps/mysite/components/hero used?"
def componentResourceType = "mysite/components/hero"

def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '/content')
  AND [sling:resourceType] = '$componentResourceType'
"""

def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()

def pages = [] as Set
results.nodes.each { node ->
    def current = node
    while (current != null && !current.isNodeType("cq:Page")) {
        current = current.parent
    }
    if (current) pages << current.path
}

println "=== Pages using: $componentResourceType ==="
println "Total pages: ${pages.size()}\n"
pages.sort().each { println "  - $it" }
```

***

### Scoped search — component usage under a specific content root

```groovy
def componentResourceType = "mysite/components/teaser"
def searchRoot = "/content/mysite"

def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '$searchRoot')
  AND [sling:resourceType] = '$componentResourceType'
"""

def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()

println "=== Component nodes: $componentResourceType under $searchRoot ===\n"
results.nodes.each { println "  ${it.path}" }
```

***

## Page Inspection

### Get page properties and template of a page

```groovy
// ✅ USE CASE: Inspect page metadata, template, title, description
def pagePath = "/content/mysite/en/home"

def pageManager = resourceResolver.adaptTo(com.day.cq.wcm.api.PageManager)
def page = pageManager.getPage(pagePath)

if (!page) { println "❌ Page not found: $pagePath"; return }

def props = page.properties
println "=== Page: $pagePath ==="
println "Title        : ${page.title}"
println "Template     : ${props.get('cq:template', 'N/A')}"
println "Last Modified: ${props.get('cq:lastModified', 'N/A')}"
println "Last Mod By  : ${props.get('cq:lastModifiedBy', 'N/A')}"
println "Created      : ${props.get('jcr:created', 'N/A')}"
println "Nav Title    : ${props.get('navTitle', 'N/A')}"
println "Description  : ${props.get('jcr:description', 'N/A')}"
println "Tags         : ${props.get('cq:tags', [])}"
println "On-time      : ${props.get('onTime', 'N/A')}"
println "Off-time     : ${props.get('offTime', 'N/A')}"
```

***

### Traverse all child pages of a root

```groovy
// ✅ USE CASE: Audit all pages in a site tree
def rootPath = "/content/mysite/en"
def maxDepth = 3
def count = 0

def traverse
traverse = { page, depth ->
    if (depth > maxDepth) return
    def indent = "  " * depth
    println "${indent}- [${page.name}] ${page.path} | Template: ${page.properties.get('cq:template','N/A')}"
    count++
    page.listChildren().each { child -> traverse(child, depth + 1) }
}

def pageManager = resourceResolver.adaptTo(com.day.cq.wcm.api.PageManager)
def root = pageManager.getPage(rootPath)
if (root) traverse(root, 0)
println "\nTotal pages: $count"
```

***

## Template Audit

### Find all pages using a specific editable template

```groovy
// ✅ USE CASE: Audit pages before modifying/deleting a template
def templatePath = "/conf/mysite/settings/wcm/templates/content-page"

def query = """
SELECT * FROM [cq:Page] AS page
WHERE ISDESCENDANTNODE(page, '/content')
  AND page.[jcr:content/cq:template] = '$templatePath'
"""

def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()

println "=== Pages using template: $templatePath ===\n"
def count = 0
results.nodes.each { println "  ${it.path}"; count++ }
println "\nTotal: $count pages"
```

***

### List all editable templates in /conf

```groovy
// ✅ USE CASE: Discover available templates per site config
def query = """
SELECT * FROM [cq:Template] AS t
WHERE ISDESCENDANTNODE(t, '/conf')
"""

def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()

println "=== Editable Templates under /conf ===\n"
results.nodes.each { node ->
    def title = node.hasProperty("jcr:title") ? node.getProperty("jcr:title").string : "(no title)"
    println "  [${title}] -> ${node.path}"
}
```

***

## JCR Node Inspection

### Read all properties of a specific JCR node

```groovy
// ✅ USE CASE: Debug what a component node stores
def nodePath = "/content/mysite/en/home/jcr:content/root/container/hero"

def resource = resourceResolver.getResource(nodePath)
if (!resource) { println "❌ Node not found: $nodePath"; return }

println "=== Properties at: $nodePath ===\n"
resource.valueMap.entrySet().sort { it.key }.each { entry ->
    println "  ${entry.key} = ${entry.value}"
}
```

***

### Find nodes with a specific property value

```groovy
// ✅ USE CASE: Find all nodes where linkURL = '/content/mysite/en/about'
def searchRoot   = "/content/mysite"
def propertyName = "linkURL"
def propertyValue = "/content/mysite/en/about"

def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '$searchRoot')
  AND [$propertyName] = '$propertyValue'
"""

def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()

println "=== Nodes with [$propertyName] = '$propertyValue' ===\n"
results.nodes.each { println "  ${it.path}" }
```

***

### Find nodes where a property contains a substring (LIKE)

```groovy
// ✅ USE CASE: Find all components referencing a DAM folder path
def searchRoot   = "/content/mysite"
def propertyName = "fileReference"
def substring    = "/content/dam/mysite/banners"

def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '$searchRoot')
  AND [$propertyName] LIKE '$substring%'
"""

def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()

println "=== Nodes with [$propertyName] LIKE '$substring%' ===\n"
results.nodes.each { node ->
    def val = node.hasProperty(propertyName) ? node.getProperty(propertyName).string : "?"
    println "  ${node.path}  =>  $val"
}
```

***

## Component Deep Audit

### Dump all component instances on a single page with their params

```groovy
// ✅ USE CASE: Full component property dump for debugging authoring content
def pagePath = "/content/mysite/en/home/jcr:content"

def root = resourceResolver.getResource(pagePath)
if (!root) { println "❌ Not found: $pagePath"; return }

def inspect
inspect = { resource, depth ->
    def indent = "  " * depth
    def rt = resource.valueMap.get("sling:resourceType", "")
    if (rt) {
        println "${indent}[${resource.name}]  sling:resourceType = $rt"
        resource.valueMap.entrySet()
            .findAll { !it.key.startsWith("jcr:") && it.key != "sling:resourceType" }
            .sort { it.key }
            .each { println "${indent}  ${it.key} = ${it.value}" }
    }
    resource.children.each { child -> inspect(child, depth + 1) }
}

inspect(root, 0)
```

***

### Count component usage across entire site (inventory report)

```groovy
// ✅ USE CASE: How many times is each component used site-wide?
def searchRoot = "/content/mysite"

def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '$searchRoot')
  AND [sling:resourceType] IS NOT NULL
"""

def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
q.limit = 50000
def results = q.execute()

def counts = [:].withDefault { 0 }
results.nodes.each { node ->
    if (node.hasProperty("sling:resourceType")) {
        counts[node.getProperty("sling:resourceType").string]++
    }
}

println "=== Component Usage Report under $searchRoot ===\n"
counts.sort { -it.value }.each { rt, c -> println "  ${String.format('%4d', c)}x  $rt" }
println "\nDistinct components: ${counts.size()}"
```

***

## Tag & Metadata Audit

### Find all pages tagged with a specific tag

```groovy
// ✅ USE CASE: Find content tagged with a business category
def targetTag  = "mysite:topic/digital-transformation"
def searchRoot = "/content/mysite"

def query = """
SELECT * FROM [cq:Page] AS page
WHERE ISDESCENDANTNODE(page, '$searchRoot')
  AND page.[jcr:content/cq:tags] = '$targetTag'
"""

def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()

println "=== Pages tagged [$targetTag] ===\n"
results.nodes.each { println "  ${it.path}" }
```

***

### Audit pages missing SEO metadata (title or description)

```groovy
// ✅ USE CASE: Detect SEO gaps before a site audit
def searchRoot = "/content/mysite/en"

def pageManager = resourceResolver.adaptTo(com.day.cq.wcm.api.PageManager)
def root = pageManager.getPage(searchRoot)
def issues = []

def traverse
traverse = { page ->
    def props       = page.properties
    def hasTitle    = props.get("jcr:title", "").trim() as boolean
    def hasDesc     = props.get("jcr:description", "").trim() as boolean
    if (!hasTitle || !hasDesc) {
        def flags = []
        if (!hasTitle) flags << "NO TITLE"
        if (!hasDesc)  flags << "NO DESCRIPTION"
        issues << "${page.path}  [${flags.join(', ')}]"
    }
    page.listChildren().each { traverse(it) }
}

if (root) traverse(root)
println "=== Pages missing SEO metadata ===\n"
issues.each { println "  $it" }
println "\nTotal issues: ${issues.size()}"
```

***

## Reference Finder

### Find all pages that link to a specific page path

```groovy
// ✅ USE CASE: Before deleting /content/mysite/en/about-us, who links to it?
def targetPath = "/content/mysite/en/about-us"
def searchRoot = "/content/mysite"

def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '$searchRoot')
  AND (
    [linkURL]   = '$targetPath'
    OR [href]   = '$targetPath'
    OR [pageLink] = '$targetPath'
    OR [ctaLink]  = '$targetPath'
  )
"""

def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()

def pages = [] as Set
results.nodes.each { node ->
    def current = node
    while (current != null && !current.isNodeType("cq:Page")) {
        current = current.parent
    }
    if (current) pages << current.path
}

println "=== Pages referencing: $targetPath ===\n"
pages.sort().each { println "  $it" }
println "\nTotal: ${pages.size()} pages"
```

***

## OSGi & Run Mode Context

### Print current run modes

```groovy
// ✅ USE CASE: Confirm environment (author/publish/dev/stage/prod)
import org.apache.sling.settings.SlingSettingsService

def settings = sling.getService(SlingSettingsService)
println "=== Sling Run Modes ==="
settings.runModes.sort().each { println "  - $it" }
```

***

## Replication Status

### Find pages that have never been activated

```groovy
// ✅ USE CASE: Find draft pages that were never published
def searchRoot = "/content/mysite/en"

def query = """
SELECT * FROM [cq:Page] AS page
WHERE ISDESCENDANTNODE(page, '$searchRoot')
"""

def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()

println "=== Pages never activated under $searchRoot ===\n"
def count = 0
results.nodes.each { node ->
    def jcrContent = node.hasNode("jcr:content") ? node.getNode("jcr:content") : null
    if (jcrContent && !jcrContent.hasProperty("cq:lastReplicationAction")) {
        println "  ${node.path}"
        count++
    }
}
println "\nNever activated: $count pages"
```

***

## Tips

| Tip                | Detail                                                                                  |
| ------------------ | --------------------------------------------------------------------------------------- |
| Limit results      | Use `q.limit = N` for large sites to prevent OOM errors                                 |
| Session            | `Session` = `javax.jcr.Session`, auto-available via `resourceResolver.adaptTo(Session)` |
| `resourceResolver` | Pre-bound variable in the Groovy console — no setup needed                              |
| `pageManager`      | `resourceResolver.adaptTo(com.day.cq.wcm.api.PageManager)`                              |
| Read-only safe     | None of these snippets call `session.save()` — safe to run anywhere                     |
| Oak performance    | Prefer `JCR-SQL2` with `ISDESCENDANTNODE` over XPath for Oak queries                    |
| Result cap         | Oak default query limit is 10,000 — always set `q.limit` explicitly                     |
