SuperAPI AI 工具集

extracting-jcr-queries

编程与开发

查看项目

更新于 10-15 10:14

技能介绍

用途

该技能用于审计 AEM/Oak 代码库,找出其中发出的每一个 JCR 查询,包括 XPath、SQL2 以及 AEM QueryBuilder 谓词,尤其是那些在运行时动态组装的查询。

资料指出,AEM/Oak 代码库中大多数真实查询并非字符串字面量,而是通过 StringBuilder 拼接、由命名常量构建的谓词映射或流式构建器在运行时组装,并通过少量 API 执行:javax.jcr.query.QueryManager / Query 或 com.day.cq.search.QueryBuilder / PredicateGroup。直接搜索查询文本会完全漏掉这些查询——例如以 sb.append("SELECT * FROM [").append(nodeType)... 构建的查询,在任何字面量形态模式下都零命中,但它是一个真实存在的查询。

主要能力

技能提供两种互补技术,需同时使用;两者都不是解析器,匹配结果应视为候选清单,需人工复核。

技术一:API 表面追踪(主要)

以 API 而非查询文本为锚点。无论查询本身如何构建,每个 JCR/QueryBuilder 查询都会经过少数几种调用形态。

  • 步骤 1:查找直接 API 入口点,匹配 getQueryManager()、.createQuery(、PredicateGroup.create(,以及 javax.jcr.query、org.apache.jackrabbit.oak.query、com.day.cq.search 的 import 语句。
  • 步骤 2:将每个命中分类为触发器或包装器。判断该调用点是否直接从请求/任务入口调用,还是位于被其他代码以通用参数(nodeType、path、谓词映射)调用的 helper/service/DAO 中。类名如 *Service、*Helper、*Dao、*Repository、*QueryUtil 以及被多处调用的方法都是包装器的信号。
  • 步骤 3:展开追踪包装器的每个调用者,使用 rg -n "WrapperClassName\b" 和 rg -n ".wrapperMethodName("。包装器本身可能被另一层包装,需重复直到到达真正的外部入口点(servlet、定时任务、事件监听器)且仓库内无进一步调用者,或已枚举所有调用点。每个不同的调用者都是一条不同的触发路径,应作为独立行记入清单。
  • 步骤 4:记录每个调用者中哪些是固定的、哪些是动态的。注明哪些参数(path、节点类型、谓词值)在该调用点是硬编码字面量,哪些是从更上层无约束传入的。无约束的节点类型/路径意味着查询的真实形态随调用者变化,这对索引覆盖很重要。

技术二:字面量形态正则(补充)

针对从未触及本仓库 Java API 调用的查询文本——硬编码在 JSP/JS/HTL、配置文件或查询字符串 URL 中——以查询文本自身的形态为锚点。纯 rg/grep,使用双引号 shell 字符串。

  • SQL2 — SELECT ... FROM [nodetype]:rg -n -i -P "\bselect\b.{0,200}?\bfrom\b\s*[[\w:.]+]"。
  • XPath — /jcr:root/...、//name[predicate] 或 //element(, NodeType)[predicate]:使用给定的 rg 模式。双斜杠和方括号谓词要求都是刻意的,用于避免匹配裸单斜杠路径片段或 // 行注释后跟无关的 array[0] 代码。element(, NodeType) 分支是必需的,因为纯 //[\w:]+ 名称标记无法匹配 element(, dam:Asset) 内部的括号/逗号。
  • QueryBuilder 查询字符串形式 — path=...&type=...(以 & 连接,如 URL)或空格连接(如存储的 dam:query/Smart Collection 谓词字符串,也使用数字前缀嵌套键如 1_group.0_property=... 或 5_relativedaterange.property=...):使用给定的 rg 模式。
  • QueryBuilder 映射/谓词形式,字面量键 — map.put("path", ...); map.put("type", ...) 或 {"path": ..., "type": ...}:使用给定的 rg -U 多行模式。

使用方式

工具要求

所有命令均针对 ripgrep(rg)编写。运行前先检查是否已安装:

command -v rg >/dev/null && echo "rg found" || echo "rg missing — use the fallback command under each pattern"

如果 rg 缺失,grep -P 不是安全的替代品。GNU grep 支持 -P,但 macOS 自带的 BSD grep 不支持——在未修改的 Mac 上 grep -P "..." 会以 grep: invalid option -- P 失败,与 rg 缺失是同一种失败模式。不要假设 grep -P 可作为直接替代;应使用每个模式下方给出的可移植 grep -E 命令(已针对 BSD grep 验证)。有一个模式(技术二中的多行谓词映射形式)完全没有基于行的回退方案。尽可能安装 rg(brew install ripgrep / apt install ripgrep)——更快、默认递归,且是运行该模式的唯一方式。

工作流

  1. 先运行技术一——它能应对动态查询构建。在回退到文本搜索之前,先建立包装器/调用者清单。
  2. 将技术二作为补充遍历,尤其是对非 Java 文件(JSP、JS、HTL、配置),这些文件没有可锚定的 Java API 调用。
  3. 对任一技术的每个命中,打开周边代码确认它确实是查询(而非注释、测试夹具或死代码),并记录它查询的对象。
  4. 合并为一份清单:构建点的 file:line、查询类型、涉及的 path/nodetype/properties,以及到达它的每条调用者/触发路径(来自技术一步骤 3)。这是索引覆盖检查的输入。

适用场景

  • 在检查 Oak 索引覆盖前建立查询清单
  • 回答“这个应用/bundle 发出了哪些查询?”
  • 排查缺少匹配索引的查询(遍历警告、慢查询日志)
  • 在迁移或重构前审计 bundle/package

限制与常见错误

  • 仅依赖字面量形态正则,在结果为空时得出“未找到查询”的结论——技术二为空并不能说明代码库是否以编程方式调用查询 API。务必运行技术一。
  • 找到包装器的查询构建点就停下,不追踪其调用者(技术一步骤 3)——同一包装器常被许多不相关的功能调用,每个都是值得单独记入清单的触发点。
  • 信任映射式正则的单个片段作为完整谓词集——它只能证明两个字面量键在附近,不能证明是哪些键或有多少键。
  • 忘记在 SQL2 模式上加 -i(select/SELECT 在实际中都出现)。
  • 只运行 XPath/SQL2 模式而跳过 QueryBuilder——在 AEM 代码中 QueryBuilder 往往是查询的大多数,而非原始 JCR 查询。
  • 在未安装 rg 时假设 grep -P 是安全替代品——GNU grep 支持它,但 macOS 自带的 BSD grep 不支持(grep: invalid option -- P),因此会以与 rg 缺失相同的方式失败。先用 command -v rg 检查,并使用每个模式下方给出的可移植 grep -E 回退方案。
  • 映射式正则只捕获调用点的字面量键字符串——如果键来自命名常量(params.put(PATH_KEY, ...)),它不会匹配;技术一通过 PredicateGroup.create( 锚点捕获这种情况。
  • 映射式正则在仓库范围运行前应按文件类型限定范围——path/type 是常见的 JSON/配置键,与 JCR 无关,结合 -U 的跨行匹配,该模式是四种中最嘈杂的;不加限定运行会从 package-lock.json、通用 .json 和 .js 中产生大量噪声。应限制到实际构建 QueryBuilder 谓词映射的语言并排除生成/第三方文件,例如 rg ... -g '.java' -g '.jsp' -g '*.js' -g '!/target/' -g '!/node_modules/' -g '!package-lock.json'。即使限定了范围,仍需要 -B3 -A8 并人工阅读。
  • 该模式在块中前两个键上标记命中。拉取 -B3 -A8 上下文并人工阅读整个谓词映射——不要仅信任片段。