mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
5 hours ago ad1c27b1dacc388e334100f0e8d4df6611fafb3f
[#1275] Fix Release Notes and Getting Started page titles (#1276)

The Release Notes and Getting Started chapter pages currently appear as
AsciiDoc filenames in navigation and default xrefs, and their browser
titles are `Untitled`. Give all four pages proper title metadata while
retaining their existing URLs, HTML fragments and combined-book chapter
hierarchy.

Use the existing `:table-caption!:` insertion point in doc-maven-plugin
3.2.2 to promote chapter headings only in the generated Antora output.
Keep chapter IDs on headings for the books and emit separate inline
anchors under `env-site`, because the default Antora UI does not render
document IDs as HTML anchors.

Add six integration checks in the `man-pages` profile's `verify` phase,
after Antora generation. They verify the expected document titles with
header-only parsing, reject a `:title:` attribute that could mask a
missing document header, and render the page bodies to check all 16
published chapter and subsection fragment IDs. They also preserve both
combined books' chapter titles, IDs and levels. Missing generated inputs
or a missing test include fail the build.

Document the checks and source conventions in `CHECKS.md`, including the
blank line required after each inline anchor and the different effect of
a block anchor on a section's automatic ID.

Fixes #1275.

## Validation

Current revision:

- All 26 documentation-module unit tests and six integration checks
passed on Java 11 and Java 27, with no failures, errors or skips.
- On Java 11, the strengthened integration checks accept the original
generated pages and reject each of three generated-input mutations:
removing the blank line after the requirements page's inline anchor,
replacing it with a block anchor, and replacing the title-promotion
attributes with `:title:`. The first two fail on the missing published
subsection ID; the third fails on the title attribute.
- `git diff --check` passed.

Earlier validation of the page-title and anchor changes:

- Reproduced four missing-title failures before the fix and four
missing-HTML-anchor failures before preserving the body anchors.
- Built an Antora 3.1.15 preview and verified browser titles,
landing-page/default xref labels, navigation labels and all 16
previously published content fragment IDs, with no duplicate IDs.
- Rebuilt both PDFs using doc-maven-plugin 3.2.2; checked their rendered
contents pages, chapter structure and named destinations.
- The attribute checker and 600 file xrefs across 72 pages passed.

The current revision changes only the integration test and `CHECKS.md`;
the Antora preview, PDFs, attribute checker and xref scan were not rerun
for this update. Validation used focused documentation goals; the full
reactor was not rerun. Existing Maven/JRuby/JDK access and deprecation
warnings remain. The live documentation site has not been deployed by
this change.
6 files modified
1 files added
203 ■■■■■ changed files
opendj-doc-generated-ref/CHECKS.md 34 ●●●●● patch | view | raw | blame | history
opendj-doc-generated-ref/pom.xml 23 ●●●●● patch | view | raw | blame | history
opendj-doc-generated-ref/src/main/asciidoc/getting-started/first-steps.adoc 7 ●●●●● patch | view | raw | blame | history
opendj-doc-generated-ref/src/main/asciidoc/getting-started/overview.adoc 8 ●●●●● patch | view | raw | blame | history
opendj-doc-generated-ref/src/main/asciidoc/release-notes/release-5.2.0.adoc 8 ●●●●● patch | view | raw | blame | history
opendj-doc-generated-ref/src/main/asciidoc/release-notes/requirements-and-upgrade.adoc 8 ●●●●● patch | view | raw | blame | history
opendj-doc-generated-ref/src/test/java/org/openidentityplatform/opendj/doc/AntoraPageTitlesIT.java 115 ●●●●● patch | view | raw | blame | history
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.
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>
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
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::
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].
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.
opendj-doc-generated-ref/src/test/java/org/openidentityplatform/opendj/doc/AntoraPageTitlesIT.java
New file
@@ -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);
    }
}