Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 9 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,25 @@ concurrency:

jobs:
build:
name: Build (${{ matrix.os }})
name: Build (Java ${{ matrix.java }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
java: ['21', '25']
Comment thread
coderabbitai[bot] marked this conversation as resolved.
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up toolchain (Java) via mise
uses: jdx/mise-action@9e7f7633ff6f6d6048a9418a68d48f288f50eb14 # v4.2.3
with:
cache: true
persist-credentials: false

- name: Cache Maven repository
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
- name: Set up Java
uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven-
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven

- name: Build and verify
run: ./mvnw --batch-mode --no-transfer-progress verify
Expand All @@ -44,6 +42,6 @@ jobs:
continue-on-error: true
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: jacoco-reports
name: jacoco-reports-java-${{ matrix.java }}
path: '**/target/site/jacoco/**'
if-no-files-found: ignore
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,24 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic
- Offline integration coverage for real and synthetic DATEX II 2.x/3.x XML and JSON feeds.
- Runnable, tested examples and separate user/contributor documentation paths.
- Documentation checks based on `mdbook-lint` and Lychee.
- Consumer-classpath verification for applications that install a single DATEX II model.
- Adversarial XML tests covering DTDs, external entities, and recursive entity expansion.

### Changed

- Documentation now reflects the per-version model architecture, complete version support, and
ownership boundaries between NAP pages, source indexes, and fixture metadata.
- Java 21 is now the consumer baseline, with Java 21 and 25 both verified in CI.
- XML and JSON facades no longer pull every generated model transitively; applications select the
version artifacts they need.
- JAXB contexts and compiled XML Schemas are reused per model version and classloader.

### Fixed

- Corrected stale v3-only statements, obsolete generated-model paths, broken links to integration
tests, and duplicated fixture headings.
- Hardened XML reading and validation so documents containing DTDs or entity declarations are
rejected before JAXB binding or schema validation.

[Unreleased]: https://github.com/juherr/datex4j/commits/main
[Keep a Changelog]: https://keepachangelog.com/en/1.1.0/
Expand Down
7 changes: 5 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ boundary between generated standards models and handwritten library code.

## Prerequisites

The project requires Java 25. [mise](https://mise.jdx.dev) provisions the JDK, and the committed
The project requires Java 21 or newer. [mise](https://mise.jdx.dev) provisions the baseline JDK,
CI also verifies Java 25, and the committed
Maven wrapper pins Maven.

```bash
Expand All @@ -31,7 +32,9 @@ Each version module owns its schemas, XJC bindings, generated classes, and a sma
facades to discover only the versions present on the classpath.

Handwritten support code lives in the facade, helper, domain, and integration modules. The
`examples` and `datex4j-integration-tests` modules verify public behavior and are not published.
`examples`, `datex4j-consumer-tests`, and `datex4j-integration-tests` modules verify public behavior
and are not published. Consumer tests deliberately install one model version and must remain free
of the all-version aggregate.

## Generated sources

Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ datex4j reads, writes, validates, and converts DATEX II publications without exp
its public facades. The SDK bundles generated models for DATEX II 2.0–2.3 and 3.0–3.7, provides
helpers for common user domains, and has no application-framework dependency.

- **Java 25**, JPMS-compatible, and framework-free.
- **Java 21+**, JPMS-compatible, and framework-free.
- **Generated DATEX II models** built reproducibly from vendored official XML Schemas.
- **Small public facades** for XML, JSON, and structured validation.
- **Optional domain modules** for traffic, SRTI, parking, EV charging, and UVAR.
Expand Down Expand Up @@ -80,8 +80,9 @@ in every version transitively. The AFIR-specific `AfirEnergyInfrastructure` and
packages are part of the 3.7 model; earlier versions expose only the modules defined by their own
root schema.

Domain builders currently target DATEX II 3.7. The XML, JSON, and validation facades can target any
bundled version when the corresponding model artifact is present.
Domain builders currently target DATEX II 3.7. The XML and validation facades can target any
bundled version when the corresponding model artifact is present. Conformant JSON fixtures cover
DATEX II 3.6 and 3.7.

See [Models and versions](docs/guides/models-and-versions.md) for dependency choices, version
selection, and the differences between DATEX II 2.x and 3.x.
Expand Down Expand Up @@ -119,7 +120,7 @@ boundaries, SPI discovery, and JPMS strategy.

## Build from source

The project requires Java 25. The committed Maven wrapper pins Maven, while
The project requires Java 21 or newer. The committed Maven wrapper pins Maven, while
[mise](https://mise.jdx.dev) provisions the JDK.

```bash
Expand Down
2 changes: 1 addition & 1 deletion datex4j-builders/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<dependencies>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-model</artifactId>
<artifactId>datex4j-model-v3_7</artifactId>
</dependency>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
Expand Down
47 changes: 47 additions & 0 deletions datex4j-consumer-tests/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j</artifactId>
<version>0.1.0-SNAPSHOT</version>
</parent>

<artifactId>datex4j-consumer-tests</artifactId>
<packaging>jar</packaging>

<name>datex4j-consumer-tests</name>
<description>Consumer-classpath tests for optional DATEX II model isolation. Not published.</description>

<properties>
<maven.deploy.skip>true</maven.deploy.skip>
<maven.install.skip>true</maven.install.skip>
</properties>

<dependencies>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-xml</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-json</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-model-v3_7</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2026 the datex4j authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.juherr.datex4j.consumer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import dev.juherr.datex4j.core.DatexVersion;
import dev.juherr.datex4j.json.DatexJson;
import dev.juherr.datex4j.model.spi.DatexModelProvider;
import dev.juherr.datex4j.xml.DatexXml;
import java.util.ServiceLoader;
import java.util.stream.StreamSupport;
import org.junit.jupiter.api.Test;

class SingleModelClasspathTest {

@Test
void exposesOnlyTheModelExplicitlySelectedByTheConsumer() {
Iterable<DatexModelProvider> providers = ServiceLoader.load(DatexModelProvider.class);

assertThat(StreamSupport.stream(providers.spliterator(), false).map(DatexModelProvider::version))
.containsExactly(DatexVersion.V3_7);
}

@Test
void createsXmlAndJsonFacadesForTheInstalledModel() {
assertThatCode(() -> DatexXml.builder().version(DatexVersion.V3_7).build())
.doesNotThrowAnyException();
assertThatCode(() -> DatexJson.builder().version(DatexVersion.V3_7).build())
.doesNotThrowAnyException();
}

@Test
void reportsAUsefulErrorForAModelThatIsNotInstalled() {
assertThatThrownBy(() -> DatexXml.builder().version(DatexVersion.V3_6).build())
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("datex4j-model-v3_6");
assertThatThrownBy(() -> DatexJson.builder().version(DatexVersion.V3_6).build())
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("datex4j-model-v3_6");
}
}
2 changes: 1 addition & 1 deletion datex4j-domain-evcharging/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</dependency>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-model</artifactId>
<artifactId>datex4j-model-v3_7</artifactId>
</dependency>

<dependency>
Expand Down
2 changes: 1 addition & 1 deletion datex4j-domain-parking/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</dependency>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-model</artifactId>
<artifactId>datex4j-model-v3_7</artifactId>
</dependency>

<dependency>
Expand Down
2 changes: 1 addition & 1 deletion datex4j-domain-srti/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</dependency>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-model</artifactId>
<artifactId>datex4j-model-v3_7</artifactId>
</dependency>

<dependency>
Expand Down
2 changes: 1 addition & 1 deletion datex4j-domain-traffic/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</dependency>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-model</artifactId>
<artifactId>datex4j-model-v3_7</artifactId>
</dependency>

<dependency>
Expand Down
2 changes: 1 addition & 1 deletion datex4j-domain-uvar/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</dependency>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-model</artifactId>
<artifactId>datex4j-model-v3_7</artifactId>
</dependency>

<dependency>
Expand Down
6 changes: 6 additions & 0 deletions datex4j-integration-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-model</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
1 change: 1 addition & 0 deletions datex4j-json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<dependency>
<groupId>dev.juherr.datex4j</groupId>
<artifactId>datex4j-model</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>dev.juherr.datex4j</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
* National Access Points (NAPs), anchored against a real-world Fintraffic AFIR fixture; it may
* need to adapt once an official JSON binding is published.
*
* <p>Both bundled DATEX II model versions are supported via {@link
* Builder#version(DatexVersion)}: pass {@link DatexVersion#V3_6} or {@link DatexVersion#V3_7} (the
* default is {@link DatexVersion#current()}).
* <p>DATEX II 3.6 and 3.7 are covered by conformant JSON fixtures. Select the model through {@link
* Builder#version(DatexVersion)} and add the matching {@code datex4j-model-vX_Y} artifact at
* runtime. The default is {@link DatexVersion#current()}.
*
* <p>Use {@link #createMapper()} for the default configuration (pretty-printed) or {@link
* #builder()} to customize it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
* DATEX II model, and maps the XML temporal types via {@link DatexTemporalModule}. Instances are
* immutable and thread-safe and may be shared across an application.
*
* <p>Any DATEX II object of any bundled version can be written; {@link #read} returns the
* <p>Any object from the configured DATEX II model can be written; {@link #read} returns the
* requested type. A {@code MessageContainer} value is the one exception: {@link #write} and {@link
* #writeToString} emit the conformant {@code payload}/{@code exchangeInformation} envelope for it
* (see {@link #readContainer}), while every other type is serialized directly with no wrapper.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,27 @@ public DatexJsonModule(DatexVersion version) {

@SuppressWarnings("unchecked")
private void registerMultilingualString(DatexVersion version) {
if (version == DatexVersion.V3_7) {
addSerializer(
dev.juherr.datex4j.model.v3_7.common.MultilingualString.class,
new MultilingualStringJson.Serializer());
addDeserializer(
dev.juherr.datex4j.model.v3_7.common.MultilingualString.class,
new MultilingualStringJson.Deserializer());
} else {
addSerializer(
dev.juherr.datex4j.model.v3_6.common.MultilingualString.class,
new MultilingualString36Json.Serializer());
addDeserializer(
dev.juherr.datex4j.model.v3_6.common.MultilingualString.class,
new MultilingualString36Json.Deserializer());
String commonPackage = MODEL_PACKAGE_PREFIX + version.packageSegment() + ".common.";
Class<?> stringType = loadModelClass(commonPackage + "MultilingualString", version);
Class<?> valueType = loadModelClass(commonPackage + "MultilingualStringValue", version);
addSerializer((Class<Object>) stringType, new MultilingualStringJson.Serializer());
addDeserializer((Class<Object>) stringType, new MultilingualStringJson.Deserializer<>(stringType, valueType));
}

private static Class<?> loadModelClass(String className, DatexVersion version) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null) {
loader = DatexJsonModule.class.getClassLoader();
}
try {
return Class.forName(className, true, loader);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(
"DATEX II model "
+ version
+ " is not available; add dev.juherr.datex4j:datex4j-model-"
+ version.packageSegment(),
e);
}
}

Expand Down
Loading
Loading