From ad1c27b1dacc388e334100f0e8d4df6611fafb3f Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Fri, 06 Nov 2026 11:12:52 +0000
Subject: [PATCH] [#1275] Fix Release Notes and Getting Started page titles (#1276)
---
opendj-doc-generated-ref/src/main/asciidoc/getting-started/first-steps.adoc | 7 +
opendj-doc-generated-ref/src/main/asciidoc/release-notes/requirements-and-upgrade.adoc | 8 ++
opendj-doc-generated-ref/pom.xml | 23 +++++
opendj-doc-generated-ref/src/main/asciidoc/getting-started/overview.adoc | 8 ++
opendj-doc-generated-ref/src/main/asciidoc/release-notes/release-5.2.0.adoc | 8 ++
opendj-doc-generated-ref/src/test/java/org/openidentityplatform/opendj/doc/AntoraPageTitlesIT.java | 115 ++++++++++++++++++++++++++++
opendj-doc-generated-ref/CHECKS.md | 34 ++++++++
7 files changed, 203 insertions(+), 0 deletions(-)
diff --git a/opendj-doc-generated-ref/CHECKS.md b/opendj-doc-generated-ref/CHECKS.md
index 79e033e..4ab34f5 100644
--- a/opendj-doc-generated-ref/CHECKS.md
+++ b/opendj-doc-generated-ref/CHECKS.md
@@ -98,3 +98,37 @@
```sh
mvn compiler:testCompile surefire:test
```
+
+The `man-pages` profile also runs `AntoraPageTitlesIT` during `verify`, after
+Antora conversion. It parses the headers of the four Release Notes and Getting
+Started chapter pages and checks their expected document titles, then renders
+their bodies with Antora's `env-site` attribute to check all 16 published chapter
+and subsection fragment IDs. A `:title:` attribute must not substitute for a document
+header: Asciidoctor can return that attribute as the title even without a header,
+but Antora does not use it.
+A section heading alone must fail: Antora needs a document header
+to populate default xref labels, navigation labels and browser titles. The tests
+also parse both preprocessed books to preserve their chapter titles, IDs and levels
+for PDF generation. Missing generated inputs fail rather than skipping the check.
+
+After preprocessing and Antora conversion, rerun this check from this module:
+
+```sh
+mvn -Pman-pages compiler:testCompile surefire:test@test-antora-page-titles
+```
+
+Chapter sources keep `==` headings for the combined books. With doc-maven-plugin
+3.2.2, keep `:table-caption!:` before each chapter heading: the Antora converter
+uses this existing attribute as the insertion point for `:leveloffset: -1`,
+which promotes the chapter heading to a standalone page title only in its output.
+Do not put the level offset directly in the book sources.
+
+The chapter ID stays on the heading outside `env-site`. On the site, it is emitted
+as a separate inline anchor after the page title: Antora's default UI does not
+render the document ID as an HTML anchor. Keep both forms so incoming fragments
+work in HTML and the combined books retain their chapter IDs. Keep a blank line
+after the anchor's `endif::[]`: Asciidoctor does not end a paragraph at a section
+title, so a heading directly below the anchor joins its paragraph and disappears
+together with its fragment ID. Do not turn the anchor into a block anchor (`[[id]]`
+or `[#id]`) either: a block anchor attaches to the next block and replaces that
+section's automatic ID.
diff --git a/opendj-doc-generated-ref/pom.xml b/opendj-doc-generated-ref/pom.xml
index 0625e54..09b186d 100644
--- a/opendj-doc-generated-ref/pom.xml
+++ b/opendj-doc-generated-ref/pom.xml
@@ -415,6 +415,29 @@
</activation>
<build><finalName>${project.groupId}.${project.artifactId}</finalName>
<plugins>
+ <!-- These tests consume the Antora pages generated during package. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>test-antora-page-titles</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>**/AntoraPageTitlesIT.java</include>
+ </includes>
+ <failIfNoTests>true</failIfNoTests>
+ <systemPropertyVariables>
+ <docBuildDirectory>${project.build.directory}/asciidoc</docBuildDirectory>
+ </systemPropertyVariables>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
<plugin>
<groupId>org.openidentityplatform.opendj</groupId>
<artifactId>opendj-doc-maven-plugin</artifactId>
diff --git a/opendj-doc-generated-ref/src/main/asciidoc/getting-started/first-steps.adoc b/opendj-doc-generated-ref/src/main/asciidoc/getting-started/first-steps.adoc
index bd66a5b..8b1ecae 100644
--- a/opendj-doc-generated-ref/src/main/asciidoc/getting-started/first-steps.adoc
+++ b/opendj-doc-generated-ref/src/main/asciidoc/getting-started/first-steps.adoc
@@ -14,11 +14,18 @@
Copyright 2026 3A Systems, LLC.
////
+:table-caption!:
:opendj-example-version: 5.2.0
+ifndef::env-site[]
[#getting-started-first-directory]
+endif::[]
== Your First Directory
+ifdef::env-site[]
+anchor:getting-started-first-directory[]
+endif::[]
+
This exercise uses the published OpenDJ {opendj-example-version} ZIP distribution on Linux or macOS with Bash. It creates a new evaluation instance, uses a self-signed certificate for LDAPS, and leaves existing installations alone. For Windows, native packages or Docker, use the xref:../install-guide/chap-install.adoc[Installation Guide].
=== Prepare the Environment
diff --git a/opendj-doc-generated-ref/src/main/asciidoc/getting-started/overview.adoc b/opendj-doc-generated-ref/src/main/asciidoc/getting-started/overview.adoc
index 69e894b..b9f0735 100644
--- a/opendj-doc-generated-ref/src/main/asciidoc/getting-started/overview.adoc
+++ b/opendj-doc-generated-ref/src/main/asciidoc/getting-started/overview.adoc
@@ -14,9 +14,17 @@
Copyright 2026 3A Systems, LLC.
////
+:table-caption!:
+
+ifndef::env-site[]
[#getting-started-overview]
+endif::[]
== Directory Concepts
+ifdef::env-site[]
+anchor:getting-started-overview[]
+endif::[]
+
OpenDJ stores directory entries and makes them available to applications through LDAP. A directory is commonly used to hold users, groups and application configuration. Applications bind to the server to authenticate, then search or update entries according to their access rights.
Entry::
diff --git a/opendj-doc-generated-ref/src/main/asciidoc/release-notes/release-5.2.0.adoc b/opendj-doc-generated-ref/src/main/asciidoc/release-notes/release-5.2.0.adoc
index d39563c..53b1961 100644
--- a/opendj-doc-generated-ref/src/main/asciidoc/release-notes/release-5.2.0.adoc
+++ b/opendj-doc-generated-ref/src/main/asciidoc/release-notes/release-5.2.0.adoc
@@ -14,9 +14,17 @@
Copyright 2026 3A Systems, LLC.
////
+:table-caption!:
+
+ifndef::env-site[]
[#release-5-2-0]
+endif::[]
== OpenDJ 5.2.0
+ifdef::env-site[]
+anchor:release-5-2-0[]
+endif::[]
+
OpenDJ 5.2.0 was published on 21 October 2026. These notes describe that released version. Documentation built from a development branch can also describe changes intended for a later release; a `SNAPSHOT` build is not a published release.
The link:https://github.com/OpenIdentityPlatform/OpenDJ/releases/tag/5.2.0[5.2.0 release record] is the authoritative list of release assets and changes. The link:https://github.com/OpenIdentityPlatform/OpenDJ/compare/5.1.2...5.2.0[complete comparison with 5.1.2] provides the corresponding source history. For newer releases, consult the link:https://github.com/OpenIdentityPlatform/OpenDJ/releases[release list].
diff --git a/opendj-doc-generated-ref/src/main/asciidoc/release-notes/requirements-and-upgrade.adoc b/opendj-doc-generated-ref/src/main/asciidoc/release-notes/requirements-and-upgrade.adoc
index 4a4cae8..a6aa151 100644
--- a/opendj-doc-generated-ref/src/main/asciidoc/release-notes/requirements-and-upgrade.adoc
+++ b/opendj-doc-generated-ref/src/main/asciidoc/release-notes/requirements-and-upgrade.adoc
@@ -14,9 +14,17 @@
Copyright 2026 3A Systems, LLC.
////
+:table-caption!:
+
+ifndef::env-site[]
[#release-requirements]
+endif::[]
== Requirements and Upgrade Considerations
+ifdef::env-site[]
+anchor:release-requirements[]
+endif::[]
+
=== Java and Installation Environment
OpenDJ 5.2.0 requires Java 11 or later. This is the minimum in the link:https://github.com/OpenIdentityPlatform/OpenDJ/blob/5.2.0/README.md[release README]; the server ZIP does not supply a Java runtime. The Docker image supplies its own Java environment.
diff --git a/opendj-doc-generated-ref/src/test/java/org/openidentityplatform/opendj/doc/AntoraPageTitlesIT.java b/opendj-doc-generated-ref/src/test/java/org/openidentityplatform/opendj/doc/AntoraPageTitlesIT.java
new file mode 100644
index 0000000..b6f559f
--- /dev/null
+++ b/opendj-doc-generated-ref/src/test/java/org/openidentityplatform/opendj/doc/AntoraPageTitlesIT.java
@@ -0,0 +1,115 @@
+/*
+ * The contents of this file are subject to the terms of the Common Development and
+ * Distribution License (the License). You may not use this file except in compliance with the
+ * License.
+ *
+ * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
+ * specific language governing permission and limitations under the License.
+ *
+ * When distributing Covered Software, include this CDDL Header Notice in each file and include
+ * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
+ * Header, with the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions copyright [year] [name of copyright owner]".
+ *
+ * Copyright 2026 3A Systems, LLC.
+ */
+package org.openidentityplatform.opendj.doc;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.asciidoctor.Asciidoctor;
+import org.asciidoctor.Options;
+import org.asciidoctor.SafeMode;
+import org.asciidoctor.ast.Document;
+import org.asciidoctor.ast.Section;
+import org.forgerock.testng.ForgeRockTestCase;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/** Checks the real Antora output after package and the chapter structure used by PDF generation. */
+@Test
+public class AntoraPageTitlesIT extends ForgeRockTestCase {
+ private Asciidoctor asciidoctor;
+ private File buildDirectory;
+
+ @BeforeClass
+ public void start() {
+ buildDirectory = new File(System.getProperty("docBuildDirectory",
+ new File(System.getProperty("basedir", "."), "target/asciidoc").getPath()));
+ asciidoctor = Asciidoctor.Factory.create();
+ }
+
+ @AfterClass(alwaysRun = true)
+ public void shutdown() {
+ if (asciidoctor != null) {
+ asciidoctor.shutdown();
+ }
+ }
+
+ @DataProvider
+ public Object[][] pages() {
+ return new Object[][] {
+ { "release-notes", "release-5.2.0", "OpenDJ 5.2.0",
+ new String[] { "release-5-2-0", "_selected_changes", "_distributions" } },
+ { "release-notes", "requirements-and-upgrade", "Requirements and Upgrade Considerations",
+ new String[] { "release-requirements", "_java_and_installation_environment", "_plan_an_upgrade",
+ "_compatibility_checks", "_known_issues_and_help" } },
+ { "getting-started", "overview", "Directory Concepts", new String[] { "getting-started-overview" } },
+ { "getting-started", "first-steps", "Your First Directory",
+ new String[] { "getting-started-first-directory", "_prepare_the_environment",
+ "_set_up_and_start_the_server", "_add_a_person", "_search_and_change_the_entry",
+ "_restart_and_remove_the_sample_data", "_next_steps" } }
+ };
+ }
+
+ @Test(dataProvider = "pages")
+ public void generatedPageHasDocumentTitle(String guide, String page, String title, String[] fragments) {
+ final File input = new File(buildDirectory, "antora/modules/" + guide + "/pages/" + page + ".adoc");
+ assertThat(input).isFile();
+ // Antora extracts metadata from the header. A full parse can fall back to a section
+ // title and conceal a missing document header, although Antora publishes "Untitled".
+ final Document document = asciidoctor.loadFile(input,
+ Options.builder().safe(SafeMode.SAFE).attributes(Map.of("env-site", ""))
+ .parseHeaderOnly(true).build());
+ assertThat(document.getDoctitle()).as("document title of %s", input).isEqualTo(title);
+ // Without a header, Asciidoctor can still return the title attribute, which Antora ignores.
+ assertThat(document.hasAttribute("title"))
+ .as("%s must have a document header rather than a :title: attribute", input).isFalse();
+ final String html = asciidoctor.convertFile(input,
+ Options.builder().safe(SafeMode.SAFE).attributes(Map.of("env-site", ""))
+ .standalone(false).toFile(false).build());
+ for (final String fragment : fragments) {
+ assertThat(html).as("published fragment %s in the rendered body of %s", fragment, input)
+ .contains("id=\"" + fragment + "\"");
+ }
+ }
+
+ @DataProvider
+ public Object[][] books() {
+ return new Object[][] {
+ { "release-notes", new String[] { "OpenDJ 5.2.0", "Requirements and Upgrade Considerations" },
+ new String[] { "release-5-2-0", "release-requirements" } },
+ { "getting-started", new String[] { "Directory Concepts", "Your First Directory" },
+ new String[] { "getting-started-overview", "getting-started-first-directory" } }
+ };
+ }
+
+ @Test(dataProvider = "books")
+ public void combinedBookKeepsChapters(String guide, String[] titles, String[] ids) {
+ final File input = new File(buildDirectory, "source/" + guide + "/index.adoc");
+ assertThat(input).isFile();
+ final Document document = asciidoctor.loadFile(input,
+ Options.builder().safe(SafeMode.UNSAFE).build());
+ final List<Section> chapters = document.getBlocks().stream()
+ .filter(Section.class::isInstance).map(Section.class::cast).collect(Collectors.toList());
+ assertThat(chapters).extracting(Section::getTitle).containsExactly(titles);
+ assertThat(chapters).extracting(Section::getId).containsExactly(ids);
+ assertThat(chapters).extracting(Section::getLevel).containsOnly(1);
+ }
+}
--
Gitblit v1.10.0