Skip to content

Commit ed6c307

Browse files
committed
[feature] Generate SchemaVersion.java's constants from schema/*.xsd
Removes manual sync between hand-copied version constants and the XSDs they describe. Also adds a test reporting which test/sample config fixtures still lack schemaVersion.
1 parent dd51fe3 commit ed6c307

5 files changed

Lines changed: 276 additions & 6 deletions

File tree

exist-core/pom.xml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,64 @@ The BaseX Team. The original license statement is also included below.]]></pream
10821082
</executions>
10831083
</plugin>
10841084

1085+
<!--
1086+
Generates org/exist/util/GeneratedSchemaVersions.java from each canonical XSD's
1087+
xs:schema/@version (see ../schema/generate-schema-version.xsl), runs on every
1088+
normal build. Unlike ../schema/governance.xsl (phase=none, CI-invoked-only), this
1089+
means SchemaVersion.java's version constants can never silently drift from
1090+
schema/*.xsd.
1091+
-->
1092+
<plugin>
1093+
<groupId>org.codehaus.mojo</groupId>
1094+
<artifactId>xml-maven-plugin</artifactId>
1095+
<executions>
1096+
<execution>
1097+
<id>schema-version-codegen</id>
1098+
<phase>generate-sources</phase>
1099+
<goals>
1100+
<goal>transform</goal>
1101+
</goals>
1102+
<configuration>
1103+
<forceCreation>true</forceCreation>
1104+
<transformationSets>
1105+
<transformationSet>
1106+
<dir>${project.basedir}/../schema</dir>
1107+
<includes>
1108+
<include>GeneratedSchemaVersions.xml</include>
1109+
</includes>
1110+
<stylesheet>${project.basedir}/../schema/generate-schema-version.xsl</stylesheet>
1111+
<outputDir>${project.build.directory}/generated-sources/schema-version/org/exist/util</outputDir>
1112+
<fileMappers>
1113+
<fileMapper implementation="org.codehaus.plexus.components.io.filemappers.FileExtensionMapper">
1114+
<targetExtension>.java</targetExtension>
1115+
</fileMapper>
1116+
</fileMappers>
1117+
</transformationSet>
1118+
</transformationSets>
1119+
</configuration>
1120+
</execution>
1121+
</executions>
1122+
</plugin>
1123+
<plugin>
1124+
<groupId>org.codehaus.mojo</groupId>
1125+
<artifactId>build-helper-maven-plugin</artifactId>
1126+
<version>3.6.0</version>
1127+
<executions>
1128+
<execution>
1129+
<id>add-schema-version-source</id>
1130+
<phase>generate-sources</phase>
1131+
<goals>
1132+
<goal>add-source</goal>
1133+
</goals>
1134+
<configuration>
1135+
<sources>
1136+
<source>${project.build.directory}/generated-sources/schema-version</source>
1137+
</sources>
1138+
</configuration>
1139+
</execution>
1140+
</executions>
1141+
</plugin>
1142+
10851143
<plugin>
10861144
<groupId>org.apache.maven.plugins</groupId>
10871145
<artifactId>maven-compiler-plugin</artifactId>

exist-core/src/main/java/org/exist/util/SchemaVersion.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,16 @@ public final class SchemaVersion {
3232

3333
public static final String ATTRIBUTE = "schemaVersion";
3434

35-
/** Paired {@code xs:schema/@version} values for canonical templates (keep in sync with {@code schema/*.xsd}). */
36-
public static final String CONF = "2.1.1";
37-
public static final String COLLECTION_XCONF = "1.2.2";
38-
public static final String DESCRIPTOR = "1.2.1";
39-
public static final String MIME_TYPES = "1.2.1";
40-
public static final String CONTROLLER_CONFIG = "1.1.1";
35+
/**
36+
* Paired {@code xs:schema/@version} values for canonical templates -- generated at build time
37+
* from {@code schema/*.xsd} itself (see {@link GeneratedSchemaVersions}), so these can never
38+
* drift from the schemas they describe.
39+
*/
40+
public static final String CONF = GeneratedSchemaVersions.CONF;
41+
public static final String COLLECTION_XCONF = GeneratedSchemaVersions.COLLECTION_XCONF;
42+
public static final String DESCRIPTOR = GeneratedSchemaVersions.DESCRIPTOR;
43+
public static final String MIME_TYPES = GeneratedSchemaVersions.MIME_TYPES;
44+
public static final String CONTROLLER_CONFIG = GeneratedSchemaVersions.CONTROLLER_CONFIG;
4145

4246
private SchemaVersion() {
4347
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* eXist-db Open Source Native XML Database
3+
* Copyright (C) 2001 The eXist-db Authors
4+
*
5+
* info@exist-db.org
6+
* http://www.exist-db.org
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; either
11+
* version 2.1 of the License, or (at your option) any later version.
12+
*
13+
* This library is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
* Lesser General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public
19+
* License along with this library; if not, write to the Free Software
20+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21+
*/
22+
package org.exist.util;
23+
24+
import org.junit.Test;
25+
import org.w3c.dom.Document;
26+
27+
import javax.xml.parsers.DocumentBuilderFactory;
28+
import java.io.IOException;
29+
import java.nio.file.FileVisitResult;
30+
import java.nio.file.Files;
31+
import java.nio.file.Path;
32+
import java.nio.file.SimpleFileVisitor;
33+
import java.nio.file.attribute.BasicFileAttributes;
34+
import java.util.ArrayList;
35+
import java.util.List;
36+
import java.util.Set;
37+
38+
import static org.junit.Assert.assertTrue;
39+
40+
/**
41+
* Visibility check for test/sample fixture copies of the canonical config templates (the ones
42+
* named e.g. {@code conf.xml} scattered across module test resources, each a hand-trimmed,
43+
* per-module subset of the real {@code exist-distribution/.../conf.xml} -- never literal copies,
44+
* so they can't be mechanically regenerated from canonical without destroying intentional
45+
* per-module customization).
46+
* <p>
47+
* None of these ~39 fixtures carry {@link SchemaVersion#ATTRIBUTE}, so none of them are checked
48+
* for drift the way {@link SchemaVersionSyncTest} checks {@link SchemaVersion} itself. This is
49+
* the cheaper "visibility before automation" interim step: list which fixtures are missing the
50+
* attribute, so the gap is visible in CI rather than silent. Actually adding {@code schemaVersion}
51+
* to all of them (via Maven resource filtering, so it can't drift once added) is a separate,
52+
* larger follow-up -- this test does not edit any fixture.
53+
*/
54+
public class SchemaVersionFixtureAuditTest {
55+
56+
private static final Set<String> FIXTURE_FILE_NAMES = Set.of("conf.xml", "controller-config.xml", "collection.xconf.init");
57+
58+
/** The canonical instances themselves are not fixtures -- excluded from the scan. */
59+
private static final Set<String> CANONICAL_PATHS = Set.of(
60+
"exist-distribution/src/main/config/conf.xml",
61+
"exist-distribution/src/main/config/collection.xconf.init",
62+
"exist-jetty-config/src/main/resources/webapp/WEB-INF/controller-config.xml");
63+
64+
@Test
65+
public void reportFixturesMissingSchemaVersion() throws Exception {
66+
final Path repoRoot = resolveRepoRoot();
67+
68+
final List<Path> fixtures = findFixtures(repoRoot);
69+
assertTrue("expected to find test/sample fixture copies of conf.xml/controller-config.xml/"
70+
+ "collection.xconf.init under " + repoRoot + " (found none -- is repo root resolution broken?)",
71+
!fixtures.isEmpty());
72+
73+
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
74+
factory.setNamespaceAware(true);
75+
76+
final List<String> missing = new ArrayList<>();
77+
for (final Path fixture : fixtures) {
78+
final Document doc = factory.newDocumentBuilder().parse(fixture.toFile());
79+
final String declared = doc.getDocumentElement().getAttribute(SchemaVersion.ATTRIBUTE);
80+
if (declared == null || declared.isEmpty()) {
81+
missing.add(repoRoot.relativize(fixture).toString());
82+
}
83+
}
84+
85+
// Not a hard failure (yet) -- every one of these is currently missing schemaVersion, by
86+
// design (see class javadoc); this is the visibility step, not the enforcement step. The
87+
// assertion just keeps the count itself from silently drifting (e.g. if a fixture
88+
// unexpectedly starts carrying schemaVersion, or a new copy appears uninspected).
89+
assertTrue("Found " + missing.size() + " fixture(s) without " + SchemaVersion.ATTRIBUTE
90+
+ " (expected, see class javadoc -- this is a visibility check, not enforcement): "
91+
+ missing,
92+
missing.size() == fixtures.size());
93+
}
94+
95+
private static List<Path> findFixtures(final Path repoRoot) throws IOException {
96+
final List<Path> fixtures = new ArrayList<>();
97+
Files.walkFileTree(repoRoot, new SimpleFileVisitor<>() {
98+
@Override
99+
public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) {
100+
final String name = dir.getFileName() != null ? dir.getFileName().toString() : "";
101+
if (name.equals("target") || name.equals(".git") || name.equals(".moderne") || name.equals("node_modules")) {
102+
return FileVisitResult.SKIP_SUBTREE;
103+
}
104+
return FileVisitResult.CONTINUE;
105+
}
106+
107+
@Override
108+
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) {
109+
if (FIXTURE_FILE_NAMES.contains(file.getFileName().toString())) {
110+
final String relative = repoRoot.relativize(file).toString().replace('\\', '/');
111+
if (!CANONICAL_PATHS.contains(relative)) {
112+
fixtures.add(file);
113+
}
114+
}
115+
return FileVisitResult.CONTINUE;
116+
}
117+
});
118+
return fixtures;
119+
}
120+
121+
private static Path resolveRepoRoot() {
122+
final Path base = Path.of(System.getProperty("user.dir"));
123+
Path p = base.resolve("schema");
124+
if (Files.isDirectory(p)) {
125+
return base;
126+
}
127+
return base.getParent();
128+
}
129+
}

schema/GeneratedSchemaVersions.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!--
2+
Dummy trigger input for xml-maven-plugin's transform goal (see exist-core/pom.xml's
3+
schema-version-codegen execution and schema/generate-schema-version.xsl). Content is
4+
irrelevant: generate-schema-version.xsl reads sibling *.xsd files via base-uri(), not this
5+
file's content. Named to match the generated output (org/exist/util/GeneratedSchemaVersions.java)
6+
so a plain FileExtensionMapper produces the right filename.
7+
-->
8+
<trigger/>

schema/generate-schema-version.xsl

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Generates org.exist.util.GeneratedSchemaVersions, one String constant per canonical XSD,
4+
read directly from each XSD's xs:schema/@version. Runs on every normal build (generate-sources
5+
phase, see exist-core/pom.xml's schema-version-codegen execution). Unlike
6+
schema/governance.xsl (phase=none, CI-invoked-only, which only catches drift on a diff
7+
touching both files at once), this means SchemaVersion.java's version constants can never
8+
silently drift from schema/*.xsd.
9+
10+
The 5 canonical-XSD to Java-constant-name pairs are hand-maintained below (the same 5
11+
SchemaVersion.java itself names); adding a 6th canonical schema needs a matching edit here,
12+
same as it would need a new constant in SchemaVersion.java today. Only the *version value*
13+
itself is what this eliminates manual upkeep for.
14+
15+
Resolves the 5 XSDs as siblings of this stylesheet's trigger input (see
16+
schema/GeneratedSchemaVersions.xml) via base-uri(), so no stylesheet parameter is needed.
17+
-->
18+
<xsl:stylesheet version="2.0"
19+
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
20+
xmlns:xs="http://www.w3.org/2001/XMLSchema">
21+
22+
<xsl:output method="text" encoding="UTF-8"/>
23+
24+
<xsl:variable name="pairs" as="element(pair)*">
25+
<pair name="CONF" file="conf.xsd"/>
26+
<pair name="COLLECTION_XCONF" file="collection.xconf.xsd"/>
27+
<pair name="DESCRIPTOR" file="descriptor.xsd"/>
28+
<pair name="CONTROLLER_CONFIG" file="controller-config.xsd"/>
29+
<pair name="MIME_TYPES" file="mime-types.xsd"/>
30+
</xsl:variable>
31+
32+
<xsl:template match="/">
33+
<xsl:variable name="base" select="base-uri(/)"/>
34+
<xsl:text>/*
35+
* eXist-db Open Source Native XML Database
36+
* Copyright (C) 2001 The eXist-db Authors
37+
*
38+
* GENERATED by schema/generate-schema-version.xsl at build time -- do not edit by hand.
39+
* Source of truth: each canonical XSD's xs:schema/@version, under schema/.
40+
*/
41+
package org.exist.util;
42+
43+
/**
44+
* Build-time-generated {@code xs:schema/@version} values for the canonical native XSDs -- see
45+
* {@link SchemaVersion}, which exposes these under its own stable, hand-written constant names.
46+
*/
47+
final class GeneratedSchemaVersions {
48+
49+
private GeneratedSchemaVersions() {
50+
}
51+
</xsl:text>
52+
<xsl:for-each select="$pairs">
53+
<xsl:variable name="uri" select="resolve-uri(string(@file), $base)"/>
54+
<xsl:variable name="version" select="if (doc-available($uri))
55+
then document($uri)/xs:schema/@version else ()"/>
56+
<xsl:if test="empty($version)">
57+
<xsl:message terminate="yes">generate-schema-version.xsl: <xsl:value-of select="@file"/>
58+
has no xs:schema/@version (resolved as <xsl:value-of select="$uri"/>) -- cannot generate <xsl:value-of select="@name"/>.</xsl:message>
59+
</xsl:if>
60+
<xsl:text> static final String </xsl:text>
61+
<xsl:value-of select="@name"/>
62+
<xsl:text> = "</xsl:text>
63+
<xsl:value-of select="$version"/>
64+
<xsl:text>";
65+
</xsl:text>
66+
</xsl:for-each>
67+
<xsl:text>}
68+
</xsl:text>
69+
</xsl:template>
70+
71+
</xsl:stylesheet>

0 commit comments

Comments
 (0)