diff --git a/.github/release-notes/0.1.0.md b/.github/release-notes/0.1.0.md new file mode 100644 index 0000000..3a629b0 --- /dev/null +++ b/.github/release-notes/0.1.0.md @@ -0,0 +1,52 @@ +# datex4j 0.1.0 + +The first datex4j release provides a modular Java 21 SDK for reading, writing, validating, and +converting DATEX II publications. + +## Highlights + +- Generated DATEX II models for versions 2.0–2.3 and 3.0–3.7. +- Version-neutral XML, conformant JSON, and XSD-validation facades. +- Builders and helpers for traffic, SRTI, parking, EV charging, UVAR, and locations. +- Generated OCPI 2.3.0 models with bidirectional charging-infrastructure mapping. +- Per-version model artifacts plus a BOM and an optional all-model aggregate. + +## Install + +Import `dev.juherr.datex4j:datex4j-bom:0.1.0`, then depend on a facade and the model versions your +application uses: + +```xml + + + + dev.juherr.datex4j + datex4j-bom + 0.1.0 + pom + import + + + + + + + dev.juherr.datex4j + datex4j-xml + + + dev.juherr.datex4j + datex4j-model-v3_7 + + +``` + +## Known limitations + +- Domain builders currently target DATEX II 3.7. +- Conformant JSON fixtures currently cover DATEX II 3.6 and 3.7. +- OCPI mapping intentionally covers charging-infrastructure data rather than every OCPI endpoint. +- As a pre-1.0 release, future minor versions may require migrations documented in the changelog. + +See the [API compatibility policy](https://github.com/juherr/datex4j/blob/0.1.0/docs/api-compatibility.md) +and the [changelog](https://github.com/juherr/datex4j/blob/0.1.0/CHANGELOG.md) for details. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c0d7aca --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,261 @@ +name: Release to Maven Central + +on: + workflow_dispatch: + inputs: + version: + description: Release version + required: true + default: 0.1.0 + type: string + +permissions: + contents: read + +concurrency: + group: maven-central-release + cancel-in-progress: false + +jobs: + publish: + name: Verify and publish + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + environment: maven-central + env: + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + RELEASE_VERSION: ${{ inputs.version }} + steps: + - name: Checkout main + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Java and signing + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5 + with: + distribution: temurin + java-version: '21' + cache: maven + server-id: central + server-username: CENTRAL_USERNAME + server-password: CENTRAL_TOKEN + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + + - name: Validate release inputs + shell: bash + run: | + set -euo pipefail + [[ "${GITHUB_REF}" == "refs/heads/main" ]] + [[ "${RELEASE_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] + actual_version=$(./mvnw --quiet help:evaluate -Dexpression=project.version -DforceStdout) + [[ "${actual_version}" == "${RELEASE_VERSION}" ]] + grep -Fq "## [${RELEASE_VERSION}] -" CHANGELOG.md + test -s ".github/release-notes/${RELEASE_VERSION}.md" + scripts/verify-revapi-allowlist.sh + test -n "${CENTRAL_USERNAME}" + test -n "${CENTRAL_TOKEN}" + test -n "${GPG_PASSPHRASE}" + + - name: Verify signing capability + shell: bash + run: | + set -euo pipefail + signing_key=$(gpg --batch --with-colons --list-secret-keys | awk -F: '$1 == "sec" { print $5; exit }') + test -n "${signing_key}" + umask 077 + GPG_PASSPHRASE_FILE="${RUNNER_TEMP}/datex4j-gpg-passphrase" + printf '%s' "${GPG_PASSPHRASE}" > "${GPG_PASSPHRASE_FILE}" + export GPG_PASSPHRASE_FILE + git config user.name "datex4j release workflow" + git config user.email "actions@users.noreply.github.com" + git config user.signingkey "${signing_key}" + git config gpg.program "$(pwd)/scripts/git-gpg-wrapper.sh" + git tag --sign --message "Signing check" "__signing-check-${GITHUB_RUN_ID}" + git tag --verify "__signing-check-${GITHUB_RUN_ID}" + git tag --delete "__signing-check-${GITHUB_RUN_ID}" + + - name: Check existing tag + shell: bash + run: | + set -euo pipefail + remote_tag=$(git ls-remote --tags origin "refs/tags/${RELEASE_VERSION}^{}" | awk '{print $1}') + if [[ -z "${remote_tag}" ]]; then + remote_tag=$(git ls-remote --tags origin "refs/tags/${RELEASE_VERSION}" | awk '{print $1}') + fi + if [[ -n "${remote_tag}" && "${remote_tag}" != "${GITHUB_SHA}" ]]; then + echo "Tag ${RELEASE_VERSION} already points to ${remote_tag}, not ${GITHUB_SHA}." >&2 + exit 1 + fi + + - name: Verify project + run: ./mvnw --batch-mode --no-transfer-progress verify + + - name: Build local Central bundle + shell: bash + run: | + set -euo pipefail + ./mvnw --batch-mode --no-transfer-progress \ + -Prelease -DskipTests -DskipPublishing=true deploy + scripts/build-central-bundle.sh target/central-bundle.zip "${RELEASE_VERSION}" + + - name: Validate Central bundle + id: bundle + shell: bash + run: | + set -euo pipefail + bundle=$(find . -type f -name central-bundle.zip -print -quit) + test -n "${bundle}" + scripts/verify-central-bundle.sh "${bundle}" "${RELEASE_VERSION}" + echo "path=${bundle}" >> "${GITHUB_OUTPUT}" + + - name: Test isolated Maven consumer + shell: bash + run: | + set -euo pipefail + bundle_repository=$(mktemp -d) + consumer_repository=$(mktemp -d) + unzip -q "${{ steps.bundle.outputs.path }}" -d "${bundle_repository}" + ./mvnw --batch-mode --no-transfer-progress \ + -f config/release-smoke/pom.xml \ + -Dmaven.repo.local="${consumer_repository}" \ + -Ddatex4j.version="${RELEASE_VERSION}" \ + -Ddatex4j.repository="file://${bundle_repository}" \ + verify + + - name: Publish to Maven Central + shell: bash + run: | + set -euo pipefail + base_url="https://repo1.maven.org/maven2/dev/juherr/datex4j" + missing=0 + while read -r artifact_id _packaging; do + [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue + pom_url="${base_url}/${artifact_id}/${RELEASE_VERSION}/${artifact_id}-${RELEASE_VERSION}.pom" + if ! curl \ + --connect-timeout 5 \ + --max-time 10 \ + --fail \ + --silent \ + --show-error \ + --head \ + "${pom_url}" >/dev/null; then + missing=1 + break + fi + done < config/release/public-artifacts.txt + if [[ "${missing}" -eq 0 ]]; then + echo "Release ${RELEASE_VERSION} is already available from Maven Central; skipping deploy." + exit 0 + fi + ./mvnw --batch-mode --no-transfer-progress \ + -Prelease -DskipTests -DautoPublish=true -DwaitUntil=published deploy + + - name: Wait for Maven Central resolution + shell: bash + run: | + set -euo pipefail + base_url="https://repo1.maven.org/maven2/dev/juherr/datex4j" + for attempt in $(seq 1 30); do + missing=0 + while read -r artifact_id _packaging; do + [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue + pom_url="${base_url}/${artifact_id}/${RELEASE_VERSION}/${artifact_id}-${RELEASE_VERSION}.pom" + if ! curl \ + --connect-timeout 5 \ + --max-time 10 \ + --fail \ + --silent \ + --show-error \ + --head \ + "${pom_url}" >/dev/null; then + missing=1 + break + fi + done < config/release/public-artifacts.txt + [[ "${missing}" -eq 0 ]] && break + if [[ "${attempt}" -eq 30 ]]; then + echo "Release did not become resolvable from Maven Central in time." >&2 + exit 1 + fi + sleep 20 + done + consumer_repository=$(mktemp -d) + ./mvnw --batch-mode --no-transfer-progress \ + -f config/release-smoke/pom.xml \ + -Dmaven.repo.local="${consumer_repository}" \ + -Ddatex4j.version="${RELEASE_VERSION}" \ + -Ddatex4j.repository="${base_url%/dev/juherr/datex4j}" \ + verify + + finalize: + name: Tag and create GitHub Release + needs: publish + runs-on: ubuntu-latest + environment: maven-central + permissions: + contents: write + env: + GH_TOKEN: ${{ github.token }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + RELEASE_VERSION: ${{ inputs.version }} + steps: + - name: Checkout published commit + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Set up Java and signing + uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5 + with: + distribution: temurin + java-version: '21' + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + + - name: Create or verify signed tag + shell: bash + run: | + set -euo pipefail + signing_key=$(gpg --batch --with-colons --list-secret-keys | awk -F: '$1 == "sec" { print $5; exit }') + test -n "${signing_key}" + umask 077 + GPG_PASSPHRASE_FILE="${RUNNER_TEMP}/datex4j-gpg-passphrase" + printf '%s' "${GPG_PASSPHRASE}" > "${GPG_PASSPHRASE_FILE}" + export GPG_PASSPHRASE_FILE + git config user.name "datex4j release workflow" + git config user.email "actions@users.noreply.github.com" + git config user.signingkey "${signing_key}" + git config gpg.program "$(pwd)/scripts/git-gpg-wrapper.sh" + + if git ls-remote --exit-code --tags origin "refs/tags/${RELEASE_VERSION}" >/dev/null 2>&1; then + git fetch --force origin "refs/tags/${RELEASE_VERSION}:refs/tags/${RELEASE_VERSION}" + tagged_commit=$(git rev-list -n 1 "${RELEASE_VERSION}") + [[ "${tagged_commit}" == "${GITHUB_SHA}" ]] + git tag --verify "${RELEASE_VERSION}" + else + git tag --sign --message "datex4j ${RELEASE_VERSION}" "${RELEASE_VERSION}" "${GITHUB_SHA}" + git tag --verify "${RELEASE_VERSION}" + git push \ + "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \ + "refs/tags/${RELEASE_VERSION}" + fi + + - name: Create or verify GitHub Release + shell: bash + run: | + set -euo pipefail + if gh release view "${RELEASE_VERSION}" >/dev/null 2>&1; then + existing_tag=$(gh release view "${RELEASE_VERSION}" --json tagName --jq .tagName) + [[ "${existing_tag}" == "${RELEASE_VERSION}" ]] + else + gh release create "${RELEASE_VERSION}" \ + --title "datex4j ${RELEASE_VERSION}" \ + --notes-file ".github/release-notes/${RELEASE_VERSION}.md" \ + --verify-tag + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 6310bf7..a91aefc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic ## [Unreleased] +## [0.1.0] - 2026-07-25 + ### Added - Generated DATEX II model artifacts for versions 2.0–2.3 and 3.0–3.7, discovered through a @@ -21,6 +23,8 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic - Adversarial XML tests covering DTDs, external entities, and recursive entity expansion. - A single contributor verification command with isolated Maven consumer checks, actionable failure logs, coverage summaries, and GitHub Actions linting. +- A documented supported API boundary and automated Revapi compatibility checks. +- A resumable, manually dispatched Maven Central publication workflow. ### Changed @@ -38,6 +42,7 @@ The format is based on [Keep a Changelog], and this project adheres to [Semantic - 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 +[Unreleased]: https://github.com/juherr/datex4j/compare/0.1.0...HEAD +[0.1.0]: https://github.com/juherr/datex4j/releases/tag/0.1.0 [Keep a Changelog]: https://keepachangelog.com/en/1.1.0/ [Semantic Versioning]: https://semver.org/spec/v2.0.0.html diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f75cbe..ddf836c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -135,28 +135,37 @@ version runbook, BOM, examples, and relevant data documentation in the same chan ## Public API stability -datex4j follows Semantic Versioning and treats public types and methods as compatibility -commitments. Document additive changes in [CHANGELOG.md](CHANGELOG.md). Highlight breaking changes -before release and include migration guidance. +datex4j follows the [API compatibility policy](docs/api-compatibility.md). Revapi checks every +module against the latest final release during `verify`; implementation packages listed in the +policy are excluded. Validate configuration changes explicitly: -## Release workflow +```bash +./mvnw revapi:validate-configuration +``` -Artifacts publish to Maven Central through the Sonatype Central Portal with the opt-in `release` -profile. The profile adds source, Javadoc, signing, and central-publishing plugins. `examples` and -`datex4j-integration-tests` are not published. +The allowlist in `config/revapi/accepted-differences.json` is intentionally empty, and the +`revapi.differences` transform is disabled while it remains empty. Every exception must enable the +transform, identify the exact difference, use a justification beginning with `Migration:`, and +repeat that migration in `CHANGELOG.md`. Run `scripts/verify-revapi-allowlist.sh` after any change +to the allowlist. -Release prerequisites remain outside the repository: +## Release workflow + +Artifacts publish to Maven Central through the manually dispatched `Release to Maven Central` +workflow. It only accepts `main`, uses the protected `maven-central` environment, validates a local +Central bundle and an isolated Maven consumer, then publishes automatically. A separate job creates +the signed tag and GitHub Release only after Central reports `published`. -- a published GPG signing key; -- Central Portal credentials in `~/.m2/settings.xml` under server id `central`. +The environment must provide `CENTRAL_USERNAME`, `CENTRAL_TOKEN`, `GPG_PRIVATE_KEY`, and +`GPG_PASSPHRASE`. The corresponding public key must be available from a public keyserver. Release steps: -1. Run `./mvnw verify` and confirm the working tree is clean. -2. Update `CHANGELOG.md`, moving relevant `Unreleased` entries into - `## [X.Y.Z] - YYYY-MM-DD`. Keep an empty `Unreleased` section and add comparison links for the - release and the next development cycle. -3. Remove `-SNAPSHOT`, set the release version, and create the release tag. -4. Run `./mvnw -Prelease deploy`. -5. Review and release the deployment in the Central Portal because `autoPublish=false`. -6. Open the next `-SNAPSHOT` version and restore an `Unreleased` changelog section. +1. Open and merge a release PR containing the final version, dated changelog section, and versioned + release notes. +2. Dispatch `Release to Maven Central` from `main` with that version. +3. Verify Central resolution, the signed tag, and the GitHub Release. +4. Open a separate PR for the next `X.Y.Z-SNAPSHOT` version and an empty `Unreleased` section. + +The workflow safely resumes when its signed tag or GitHub Release already exists at the same +commit. It refuses conflicting tags or releases. diff --git a/README.md b/README.md index 749798c..ff6839c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ helpers for common user domains, and has no application-framework dependency. - **Optional domain modules** for traffic, SRTI, parking, EV charging, and UVAR. - **Apache-2.0** licensed and versioned as a multi-module SDK. -> Project status: `0.1.0-SNAPSHOT`. The architecture and public facades are in place, while domain +> Project status: `0.1.0`. The architecture and public facades are in place, while domain > convenience APIs and DATEX II profile support continue to grow. ## Quick start @@ -26,7 +26,7 @@ facade and model dependencies that your application needs. dev.juherr.datex4j datex4j-bom - 0.1.0-SNAPSHOT + 0.1.0 pom import @@ -108,6 +108,8 @@ boundaries, SPI discovery, and JPMS strategy. - [XML, JSON, and validation](docs/guides/xml-json-validation.md) — public facade usage. - [Domain builders and locations](docs/guides/domain-builders-and-location.md) — convenience APIs. - [OCPI mapping](docs/guides/ocpi-mapping.md) — supported mappings and known limitations. +- [API compatibility](docs/api-compatibility.md) — supported types, extensions, and migration + policy. - [AFIR / NAP knowledge base](docs/afir/README.md) — regulation, country access points, and datasets. - [Open DATEX II test data](docs/datex-test-data-sources.md) — cross-domain source catalogue. diff --git a/config/release-smoke/pom.xml b/config/release-smoke/pom.xml new file mode 100644 index 0000000..03dcda0 --- /dev/null +++ b/config/release-smoke/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + dev.juherr.datex4j.release + datex4j-release-smoke + 1 + + datex4j release smoke test + Standalone consumer used to verify staged and published datex4j artifacts. + https://github.com/juherr/datex4j + + + 21 + UTF-8 + 0.1.0 + https://repo1.maven.org/maven2 + + + + + datex4j-release-under-test + ${datex4j.repository} + + + + + + dev.juherr.datex4j + datex4j-xml + ${datex4j.version} + + + dev.juherr.datex4j + datex4j-model-v3_7 + ${datex4j.version} + + + org.junit.jupiter + junit-jupiter + 6.1.2 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.15.0 + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.6 + + + + diff --git a/config/release-smoke/src/test/java/dev/juherr/datex4j/release/PublishedConsumerTest.java b/config/release-smoke/src/test/java/dev/juherr/datex4j/release/PublishedConsumerTest.java new file mode 100644 index 0000000..4fe7397 --- /dev/null +++ b/config/release-smoke/src/test/java/dev/juherr/datex4j/release/PublishedConsumerTest.java @@ -0,0 +1,40 @@ +/* + * 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.release; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dev.juherr.datex4j.core.DatexVersion; +import dev.juherr.datex4j.model.spi.DatexModelProvider; +import dev.juherr.datex4j.xml.DatexXml; +import java.util.List; +import java.util.ServiceLoader; +import org.junit.jupiter.api.Test; + +class PublishedConsumerTest { + + @Test + void resolvesAndRunsWithOneSelectedModel() { + List versions = ServiceLoader.load(DatexModelProvider.class).stream() + .map(ServiceLoader.Provider::get) + .map(DatexModelProvider::version) + .toList(); + + assertEquals(List.of(DatexVersion.V3_7), versions); + assertDoesNotThrow(() -> DatexXml.builder().version(DatexVersion.V3_7).build()); + } +} diff --git a/config/release/public-artifacts.txt b/config/release/public-artifacts.txt new file mode 100644 index 0000000..b192b8c --- /dev/null +++ b/config/release/public-artifacts.txt @@ -0,0 +1,29 @@ +# artifactId packaging +datex4j pom +datex4j-core jar +datex4j-model-spi jar +datex4j-model-v2_0 jar +datex4j-model-v2_1 jar +datex4j-model-v2_2 jar +datex4j-model-v2_3 jar +datex4j-model-v3_0 jar +datex4j-model-v3_1 jar +datex4j-model-v3_2 jar +datex4j-model-v3_3 jar +datex4j-model-v3_4 jar +datex4j-model-v3_5 jar +datex4j-model-v3_6 jar +datex4j-model-v3_7 jar +datex4j-model jar +datex4j-bom pom +datex4j-xml jar +datex4j-builders jar +datex4j-location jar +datex4j-domain-traffic jar +datex4j-domain-srti jar +datex4j-domain-parking jar +datex4j-domain-evcharging jar +datex4j-domain-uvar jar +datex4j-json jar +datex4j-validation jar +datex4j-ocpi jar diff --git a/config/revapi/accepted-differences.json b/config/revapi/accepted-differences.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/config/revapi/accepted-differences.json @@ -0,0 +1 @@ +[] diff --git a/config/revapi/revapi.json b/config/revapi/revapi.json new file mode 100644 index 0000000..35fb4a5 --- /dev/null +++ b/config/revapi/revapi.json @@ -0,0 +1,43 @@ +[ + { + "extension": "revapi.filter", + "configuration": { + "elements": { + "exclude": [ + { + "matcher": "java-package", + "match": "/dev\\.juherr\\.datex4j(?:\\..*)?\\.internal(?:\\..*)?/" + }, + { + "matcher": "java-package", + "match": "/dev\\.juherr\\.datex4j\\.ocpi\\.support(?:\\..*)?/" + }, + { + "matcher": "java-package", + "match": "/dev\\.juherr\\.datex4j\\.model\\.v[23]_[0-9]+\\.spi/" + }, + { + "matcher": "java", + "match": "class ^dev.juherr.datex4j.xml.SecureXmlSource {}" + } + ] + } + } + }, + { + "extension": "revapi.reporter.text", + "configuration": {} + }, + { + "extension": "revapi.java", + "configuration": { + "missing-classes": { + "behavior": "ignore" + } + } + }, + { + "extension": "revapi.java.downplayHarmlessAnnotationChanges", + "configuration": true + } +] diff --git a/datex4j-bom/pom.xml b/datex4j-bom/pom.xml index 60a8906..ba0ae08 100644 --- a/datex4j-bom/pom.xml +++ b/datex4j-bom/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-bom diff --git a/datex4j-builders/pom.xml b/datex4j-builders/pom.xml index 98dcb02..d14a644 100644 --- a/datex4j-builders/pom.xml +++ b/datex4j-builders/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-builders diff --git a/datex4j-consumer-tests/pom.xml b/datex4j-consumer-tests/pom.xml index 52e150c..321ed3e 100644 --- a/datex4j-consumer-tests/pom.xml +++ b/datex4j-consumer-tests/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-consumer-tests diff --git a/datex4j-core/pom.xml b/datex4j-core/pom.xml index 41595d3..f400a63 100644 --- a/datex4j-core/pom.xml +++ b/datex4j-core/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-core diff --git a/datex4j-domain-evcharging/pom.xml b/datex4j-domain-evcharging/pom.xml index 6580253..906abb5 100644 --- a/datex4j-domain-evcharging/pom.xml +++ b/datex4j-domain-evcharging/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-domain-evcharging diff --git a/datex4j-domain-parking/pom.xml b/datex4j-domain-parking/pom.xml index 8da9846..6c76b8b 100644 --- a/datex4j-domain-parking/pom.xml +++ b/datex4j-domain-parking/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-domain-parking diff --git a/datex4j-domain-srti/pom.xml b/datex4j-domain-srti/pom.xml index fc0a0f1..34432e9 100644 --- a/datex4j-domain-srti/pom.xml +++ b/datex4j-domain-srti/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-domain-srti diff --git a/datex4j-domain-traffic/pom.xml b/datex4j-domain-traffic/pom.xml index d58f3da..6cdb0cf 100644 --- a/datex4j-domain-traffic/pom.xml +++ b/datex4j-domain-traffic/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-domain-traffic diff --git a/datex4j-domain-uvar/pom.xml b/datex4j-domain-uvar/pom.xml index e68369a..192b126 100644 --- a/datex4j-domain-uvar/pom.xml +++ b/datex4j-domain-uvar/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-domain-uvar diff --git a/datex4j-integration-tests/pom.xml b/datex4j-integration-tests/pom.xml index 5b9a8ac..eb9e244 100644 --- a/datex4j-integration-tests/pom.xml +++ b/datex4j-integration-tests/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-integration-tests diff --git a/datex4j-json/pom.xml b/datex4j-json/pom.xml index 2117eeb..cc771c8 100644 --- a/datex4j-json/pom.xml +++ b/datex4j-json/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-json diff --git a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/DatexEnumJson.java b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/DatexEnumJson.java index fb34b57..1c0c34c 100644 --- a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/DatexEnumJson.java +++ b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/DatexEnumJson.java @@ -42,7 +42,7 @@ * the generated model; callers register it per wrapper type. The corresponding deserialization * direction is handled by {@link DatexJsonModule}'s own contextual deserializer. */ -public final class DatexEnumJson { +final class DatexEnumJson { private DatexEnumJson() {} diff --git a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/DatexPrefixes.java b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/DatexPrefixes.java index a7fcdd1..6b92955 100644 --- a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/DatexPrefixes.java +++ b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/DatexPrefixes.java @@ -30,7 +30,7 @@ * datex4j-model/src/main/resources/META-INF/datex4j/schema/v3.7/*.xsd | sort -u * } */ -public final class DatexPrefixes { +final class DatexPrefixes { private static final String BASE = "http://datex2.eu/schema/3/"; diff --git a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/GAttributes.java b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/GAttributes.java index 45d9ab1..a3c2ed9 100644 --- a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/GAttributes.java +++ b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/GAttributes.java @@ -28,7 +28,7 @@ * shows {@code idG}, {@code versionG}, and {@code modelBaseVersionG} but leaves locally declared * attributes such as {@code lang}, {@code order}, and {@code extensionName} bare. */ -public final class GAttributes { +final class GAttributes { private static final Set GLOBAL_GROUP = Set.of("id", "version", "modelBaseVersion"); diff --git a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/MultilingualStringJson.java b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/MultilingualStringJson.java index f9b6d13..a4dfca0 100644 --- a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/MultilingualStringJson.java +++ b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/MultilingualStringJson.java @@ -38,7 +38,7 @@ * *
{@code {"values":[{"lang":"fi","value":"Kärkitie 4"}]}}
*/ -public final class MultilingualStringJson { +final class MultilingualStringJson { private MultilingualStringJson() {} diff --git a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/SubstitutionJson.java b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/SubstitutionJson.java index e2213f6..e1beff0 100644 --- a/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/SubstitutionJson.java +++ b/datex4j-json/src/main/java/dev/juherr/datex4j/json/internal/SubstitutionJson.java @@ -29,7 +29,7 @@ * fixture ({@code * datex4j-json/src/test/resources/datex-json/finland-afir-messagecontainer.v3_6.json}). */ -public final class SubstitutionJson { +final class SubstitutionJson { private SubstitutionJson() {} diff --git a/datex4j-location/pom.xml b/datex4j-location/pom.xml index 1e56a79..42c3eb4 100644 --- a/datex4j-location/pom.xml +++ b/datex4j-location/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-location diff --git a/datex4j-model-spi/pom.xml b/datex4j-model-spi/pom.xml index da81ca5..bc7258e 100644 --- a/datex4j-model-spi/pom.xml +++ b/datex4j-model-spi/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-spi diff --git a/datex4j-model-v2_0/pom.xml b/datex4j-model-v2_0/pom.xml index 49742b1..22cc5f5 100644 --- a/datex4j-model-v2_0/pom.xml +++ b/datex4j-model-v2_0/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v2_0 diff --git a/datex4j-model-v2_0/src/main/java/dev/juherr/datex4j/model/v2_0/spi/package-info.java b/datex4j-model-v2_0/src/main/java/dev/juherr/datex4j/model/v2_0/spi/package-info.java new file mode 100644 index 0000000..bb26c8e --- /dev/null +++ b/datex4j-model-v2_0/src/main/java/dev/juherr/datex4j/model/v2_0/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 2.0 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v2_0.spi; diff --git a/datex4j-model-v2_1/pom.xml b/datex4j-model-v2_1/pom.xml index 46172dc..12f0e49 100644 --- a/datex4j-model-v2_1/pom.xml +++ b/datex4j-model-v2_1/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v2_1 diff --git a/datex4j-model-v2_1/src/main/java/dev/juherr/datex4j/model/v2_1/spi/package-info.java b/datex4j-model-v2_1/src/main/java/dev/juherr/datex4j/model/v2_1/spi/package-info.java new file mode 100644 index 0000000..3e10a30 --- /dev/null +++ b/datex4j-model-v2_1/src/main/java/dev/juherr/datex4j/model/v2_1/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 2.1 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v2_1.spi; diff --git a/datex4j-model-v2_2/pom.xml b/datex4j-model-v2_2/pom.xml index 3125470..a6cac66 100644 --- a/datex4j-model-v2_2/pom.xml +++ b/datex4j-model-v2_2/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v2_2 diff --git a/datex4j-model-v2_2/src/main/java/dev/juherr/datex4j/model/v2_2/spi/package-info.java b/datex4j-model-v2_2/src/main/java/dev/juherr/datex4j/model/v2_2/spi/package-info.java new file mode 100644 index 0000000..0bec818 --- /dev/null +++ b/datex4j-model-v2_2/src/main/java/dev/juherr/datex4j/model/v2_2/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 2.2 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v2_2.spi; diff --git a/datex4j-model-v2_3/pom.xml b/datex4j-model-v2_3/pom.xml index 4deae7a..607399f 100644 --- a/datex4j-model-v2_3/pom.xml +++ b/datex4j-model-v2_3/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v2_3 diff --git a/datex4j-model-v2_3/src/main/java/dev/juherr/datex4j/model/v2_3/spi/package-info.java b/datex4j-model-v2_3/src/main/java/dev/juherr/datex4j/model/v2_3/spi/package-info.java new file mode 100644 index 0000000..96880ca --- /dev/null +++ b/datex4j-model-v2_3/src/main/java/dev/juherr/datex4j/model/v2_3/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 2.3 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v2_3.spi; diff --git a/datex4j-model-v3_0/pom.xml b/datex4j-model-v3_0/pom.xml index 7d1f6d8..9a4449d 100644 --- a/datex4j-model-v3_0/pom.xml +++ b/datex4j-model-v3_0/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v3_0 diff --git a/datex4j-model-v3_0/src/main/java/dev/juherr/datex4j/model/v3_0/spi/package-info.java b/datex4j-model-v3_0/src/main/java/dev/juherr/datex4j/model/v3_0/spi/package-info.java new file mode 100644 index 0000000..3b18875 --- /dev/null +++ b/datex4j-model-v3_0/src/main/java/dev/juherr/datex4j/model/v3_0/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 3.0 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v3_0.spi; diff --git a/datex4j-model-v3_1/pom.xml b/datex4j-model-v3_1/pom.xml index e733844..b37866d 100644 --- a/datex4j-model-v3_1/pom.xml +++ b/datex4j-model-v3_1/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v3_1 diff --git a/datex4j-model-v3_1/src/main/java/dev/juherr/datex4j/model/v3_1/spi/package-info.java b/datex4j-model-v3_1/src/main/java/dev/juherr/datex4j/model/v3_1/spi/package-info.java new file mode 100644 index 0000000..eeb2dcc --- /dev/null +++ b/datex4j-model-v3_1/src/main/java/dev/juherr/datex4j/model/v3_1/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 3.1 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v3_1.spi; diff --git a/datex4j-model-v3_2/pom.xml b/datex4j-model-v3_2/pom.xml index b3a50a3..fa6dec9 100644 --- a/datex4j-model-v3_2/pom.xml +++ b/datex4j-model-v3_2/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v3_2 diff --git a/datex4j-model-v3_2/src/main/java/dev/juherr/datex4j/model/v3_2/spi/package-info.java b/datex4j-model-v3_2/src/main/java/dev/juherr/datex4j/model/v3_2/spi/package-info.java new file mode 100644 index 0000000..89c69be --- /dev/null +++ b/datex4j-model-v3_2/src/main/java/dev/juherr/datex4j/model/v3_2/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 3.2 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v3_2.spi; diff --git a/datex4j-model-v3_3/pom.xml b/datex4j-model-v3_3/pom.xml index d068b07..fbc1c8a 100644 --- a/datex4j-model-v3_3/pom.xml +++ b/datex4j-model-v3_3/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v3_3 diff --git a/datex4j-model-v3_3/src/main/java/dev/juherr/datex4j/model/v3_3/spi/package-info.java b/datex4j-model-v3_3/src/main/java/dev/juherr/datex4j/model/v3_3/spi/package-info.java new file mode 100644 index 0000000..689c41e --- /dev/null +++ b/datex4j-model-v3_3/src/main/java/dev/juherr/datex4j/model/v3_3/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 3.3 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v3_3.spi; diff --git a/datex4j-model-v3_4/pom.xml b/datex4j-model-v3_4/pom.xml index 14cc8e0..79df701 100644 --- a/datex4j-model-v3_4/pom.xml +++ b/datex4j-model-v3_4/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v3_4 diff --git a/datex4j-model-v3_4/src/main/java/dev/juherr/datex4j/model/v3_4/spi/package-info.java b/datex4j-model-v3_4/src/main/java/dev/juherr/datex4j/model/v3_4/spi/package-info.java new file mode 100644 index 0000000..4bab9be --- /dev/null +++ b/datex4j-model-v3_4/src/main/java/dev/juherr/datex4j/model/v3_4/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 3.4 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v3_4.spi; diff --git a/datex4j-model-v3_5/pom.xml b/datex4j-model-v3_5/pom.xml index 7c08b5c..6c3ef00 100644 --- a/datex4j-model-v3_5/pom.xml +++ b/datex4j-model-v3_5/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v3_5 diff --git a/datex4j-model-v3_5/src/main/java/dev/juherr/datex4j/model/v3_5/spi/package-info.java b/datex4j-model-v3_5/src/main/java/dev/juherr/datex4j/model/v3_5/spi/package-info.java new file mode 100644 index 0000000..66ad3d3 --- /dev/null +++ b/datex4j-model-v3_5/src/main/java/dev/juherr/datex4j/model/v3_5/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 3.5 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v3_5.spi; diff --git a/datex4j-model-v3_6/pom.xml b/datex4j-model-v3_6/pom.xml index ae311c3..664ef57 100644 --- a/datex4j-model-v3_6/pom.xml +++ b/datex4j-model-v3_6/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v3_6 diff --git a/datex4j-model-v3_6/src/main/java/dev/juherr/datex4j/model/v3_6/spi/package-info.java b/datex4j-model-v3_6/src/main/java/dev/juherr/datex4j/model/v3_6/spi/package-info.java new file mode 100644 index 0000000..25fd2c1 --- /dev/null +++ b/datex4j-model-v3_6/src/main/java/dev/juherr/datex4j/model/v3_6/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 3.6 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v3_6.spi; diff --git a/datex4j-model-v3_7/pom.xml b/datex4j-model-v3_7/pom.xml index b1a592f..b3f5891 100644 --- a/datex4j-model-v3_7/pom.xml +++ b/datex4j-model-v3_7/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model-v3_7 diff --git a/datex4j-model-v3_7/src/main/java/dev/juherr/datex4j/model/v3_7/spi/package-info.java b/datex4j-model-v3_7/src/main/java/dev/juherr/datex4j/model/v3_7/spi/package-info.java new file mode 100644 index 0000000..9c954e9 --- /dev/null +++ b/datex4j-model-v3_7/src/main/java/dev/juherr/datex4j/model/v3_7/spi/package-info.java @@ -0,0 +1,7 @@ +/** + * DATEX II 3.7 provider implementation loaded through {@link java.util.ServiceLoader}. + * + *

This package is an implementation detail. Consumers should use {@link + * dev.juherr.datex4j.model.spi.DatexModelProvider} instead. + */ +package dev.juherr.datex4j.model.v3_7.spi; diff --git a/datex4j-model/pom.xml b/datex4j-model/pom.xml index 0af3065..57fb4b5 100644 --- a/datex4j-model/pom.xml +++ b/datex4j-model/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-model @@ -81,7 +81,7 @@ - + org.apache.maven.plugins maven-jar-plugin @@ -94,7 +94,17 @@ - + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + package + + + + org.jacoco jacoco-maven-plugin diff --git a/datex4j-model/src/main/java/dev/juherr/datex4j/model/AggregateMarker.java b/datex4j-model/src/main/java/dev/juherr/datex4j/model/AggregateMarker.java new file mode 100644 index 0000000..f41d248 --- /dev/null +++ b/datex4j-model/src/main/java/dev/juherr/datex4j/model/AggregateMarker.java @@ -0,0 +1,25 @@ +/* + * 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.model; + +/** + * Gives the aggregate JAR a documentable package without adding a supported runtime type. + * + *

The class is deliberately package-private. + */ +final class AggregateMarker { + private AggregateMarker() {} +} diff --git a/datex4j-model/src/main/java/dev/juherr/datex4j/model/package-info.java b/datex4j-model/src/main/java/dev/juherr/datex4j/model/package-info.java new file mode 100644 index 0000000..01e429e --- /dev/null +++ b/datex4j-model/src/main/java/dev/juherr/datex4j/model/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Convenience aggregate for all version-specific DATEX II model artifacts. + * + *

This package declares no supported runtime types. The artifact exists to bring every {@code + * datex4j-model-vX_Y} dependency onto the consumer classpath. + */ +package dev.juherr.datex4j.model; diff --git a/datex4j-ocpi/pom.xml b/datex4j-ocpi/pom.xml index 31e82d9..394f9ed 100644 --- a/datex4j-ocpi/pom.xml +++ b/datex4j-ocpi/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-ocpi diff --git a/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/mapping/internal/package-info.java b/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/mapping/internal/package-info.java new file mode 100644 index 0000000..7e0b4b5 --- /dev/null +++ b/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/mapping/internal/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Unsupported implementation details shared by the public OCPI mapping facades. + * + *

Types in this package are not part of datex4j's supported API and may change without + * deprecation. + */ +package dev.juherr.datex4j.ocpi.mapping.internal; diff --git a/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/support/package-info.java b/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/support/package-info.java new file mode 100644 index 0000000..561f459 --- /dev/null +++ b/datex4j-ocpi/src/main/java/dev/juherr/datex4j/ocpi/support/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Unsupported support code generated for the OCPI model. + * + *

Applications should use the generated {@code dev.juherr.datex4j.ocpi.model.v2_3} types and + * the public mapping facades. Types in this package may change when the generator changes. + */ +package dev.juherr.datex4j.ocpi.support; diff --git a/datex4j-validation/pom.xml b/datex4j-validation/pom.xml index f574edf..91b5f2b 100644 --- a/datex4j-validation/pom.xml +++ b/datex4j-validation/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-validation diff --git a/datex4j-xml/pom.xml b/datex4j-xml/pom.xml index f16e31d..e70b262 100644 --- a/datex4j-xml/pom.xml +++ b/datex4j-xml/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-xml diff --git a/datex4j-xml/src/main/java/dev/juherr/datex4j/xml/SecureXmlSource.java b/datex4j-xml/src/main/java/dev/juherr/datex4j/xml/SecureXmlSource.java index 10dc12e..1291c17 100644 --- a/datex4j-xml/src/main/java/dev/juherr/datex4j/xml/SecureXmlSource.java +++ b/datex4j-xml/src/main/java/dev/juherr/datex4j/xml/SecureXmlSource.java @@ -25,7 +25,13 @@ import org.xml.sax.SAXException; import org.xml.sax.XMLReader; -/** Creates fail-closed SAX sources for untrusted DATEX II XML documents. */ +/** + * Creates fail-closed SAX sources for untrusted DATEX II XML documents. + * + *

API note: This public type exists for cooperation between datex4j modules. It + * is an unsupported implementation detail and may change without notice. Applications should use + * {@link DatexXml} or the validation facade instead. + */ public final class SecureXmlSource { private static final String DISALLOW_DOCTYPE = "http://apache.org/xml/features/disallow-doctype-decl"; diff --git a/docs/README.md b/docs/README.md index 4279997..42d9a1f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,6 +14,7 @@ goal. convenience APIs. 5. Read [OCPI mapping](guides/ocpi-mapping.md) when converting charging infrastructure between OCPI 2.3 and DATEX II 3.7. +6. Read [API compatibility](api-compatibility.md) before relying on extension points or upgrading. The [examples module](../examples/src/main/java/dev/juherr/datex4j/examples) contains runnable, tested sources for every guide. @@ -27,6 +28,8 @@ tested sources for every guide. 4. Read the [integration-test guide](../datex4j-integration-tests/README.md) before adding a fixture or live-feed test. 5. Record user-visible changes in [CHANGELOG.md](../CHANGELOG.md). +6. Follow [API compatibility](api-compatibility.md) when changing public types or accepting a + Revapi difference. ## Standards and test data diff --git a/docs/api-compatibility.md b/docs/api-compatibility.md new file mode 100644 index 0000000..8f6237e --- /dev/null +++ b/docs/api-compatibility.md @@ -0,0 +1,51 @@ +# API compatibility + +datex4j uses Semantic Versioning to communicate source and binary compatibility. Before `1.0.0`, +minor releases may contain breaking changes, but every such change is announced in the changelog +with explicit migration guidance. Patch releases preserve the supported API. + +## Supported API + +Compatibility checks cover: + +- the XML, JSON, and validation facades; +- builders, location helpers, and domain convenience APIs; +- the version-neutral model SPI; +- generated DATEX II model classes for every published model artifact; +- generated OCPI model classes and the public OCPI mapping API. + +Applications may implement `DatexModelProvider` to integrate an additional DATEX II version. This +is the supported extension point. Generated models remain versioned by artifact and Java package, +so applications can migrate one model version at a time. + +## Unsupported implementation details + +The following types remain accessible where Java or inter-module integration requires it, but are +not compatibility commitments: + +- every package named `internal` and its subpackages; +- `dev.juherr.datex4j.ocpi.support`; +- each `dev.juherr.datex4j.model.vX_Y.spi.DatexModelProviderVXY` implementation; +- `dev.juherr.datex4j.xml.SecureXmlSource`. + +Do not import these types from application code. Use the public facades, mapping APIs, and +`DatexModelProvider` contract instead. + +## Compatibility enforcement + +Maven runs Revapi during `verify` and compares each module with its most recent final release. +Dependencies are checked in their owning modules rather than repeatedly in consumers. Before the +first release, an unresolved baseline is allowed; once `0.1.0` is available, it automatically +becomes the baseline for `0.2.0-SNAPSHOT`. + +The versioned allowlist in `config/revapi/accepted-differences.json` is empty by default, so the +`revapi.differences` transform is disabled. When an exception is required, enable the transform, +add its entries to the analysis configuration, and keep the allowlist synchronized. A +compatibility exception must: + +1. identify the exact Revapi difference; +2. provide a justification beginning with `Migration:`; +3. describe the same migration in `CHANGELOG.md`. + +Prefer deprecation and an additive replacement before removal. When a breaking change is necessary +during `0.x`, retain the old API for at least one minor release whenever practical. diff --git a/examples/pom.xml b/examples/pom.xml index 7683fee..40dfec5 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -7,7 +7,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 datex4j-examples diff --git a/pom.xml b/pom.xml index ec3032f..9ffd831 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ dev.juherr.datex4j datex4j - 0.1.0-SNAPSHOT + 0.1.0 pom datex4j @@ -99,12 +99,16 @@ 2.96.0 0.8.15 0.00 + 0.15.1 + 0.28.4 3.4.0 3.12.0 3.2.8 0.11.0 + false + validated @@ -383,6 +387,61 @@ + + + + org.revapi + revapi-maven-plugin + ${revapi-maven-plugin.version} + + + org.revapi + revapi-java + ${revapi-java.version} + + + + RELEASE + [0-9]+\.[0-9]+\.[0-9]+ + false + true + false + + + + revapi.java.filter.annotated + + + + + revapi.differences + revapi.ignore + revapi.reclassify + revapi.semver.ignore + revapi.versions + + + + + + + ${maven.multiModuleProjectDirectory}/config/revapi/revapi.json + ${maven.multiModuleProjectDirectory}/config/revapi/accepted-differences.json + + + + + check-api-compatibility + verify + + check + + + + @@ -417,7 +476,7 @@ none true - false + true @@ -451,8 +510,8 @@ true central - - false + ${central.autoPublish} + ${central.waitUntil} diff --git a/scripts/build-central-bundle.sh b/scripts/build-central-bundle.sh new file mode 100755 index 0000000..663d66e --- /dev/null +++ b/scripts/build-central-bundle.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 2 ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +output=$1 +version=$2 +project_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +inventory="${project_root}/config/release/public-artifacts.txt" + +if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Invalid release version: ${version}" >&2 + exit 2 +fi + +mkdir -p "$(dirname "${output}")" +output_directory=$(cd "$(dirname "${output}")" && pwd) +output="${output_directory}/$(basename "${output}")" + +staging=$(mktemp -d) +trap 'rm -rf "${staging}"' EXIT +repository_root="${staging}/dev/juherr/datex4j" + +copy_payload() { + local source=$1 + local destination=$2 + + if [[ ! -f "${source}" ]]; then + echo "Missing built release payload: ${source}" >&2 + exit 1 + fi + if [[ ! -f "${source}.asc" ]]; then + echo "Missing built release signature: ${source}.asc" >&2 + exit 1 + fi + + cp "${source}" "${destination}" + cp "${source}.asc" "${destination}.asc" +} + +while read -r artifact_id packaging; do + [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue + + module_root="${project_root}/${artifact_id}" + if [[ "${artifact_id}" == "datex4j" ]]; then + module_root="${project_root}" + fi + artifact_root="${repository_root}/${artifact_id}/${version}" + base="${artifact_id}-${version}" + mkdir -p "${artifact_root}" + + copy_payload "${module_root}/target/${base}.pom" "${artifact_root}/${base}.pom" + if [[ "${packaging}" == "jar" ]]; then + copy_payload "${module_root}/target/${base}.jar" "${artifact_root}/${base}.jar" + copy_payload \ + "${module_root}/target/${base}-sources.jar" \ + "${artifact_root}/${base}-sources.jar" + copy_payload \ + "${module_root}/target/${base}-javadoc.jar" \ + "${artifact_root}/${base}-javadoc.jar" + elif [[ "${packaging}" != "pom" ]]; then + echo "Unsupported packaging '${packaging}' for ${artifact_id}" >&2 + exit 1 + fi +done < "${inventory}" + +while IFS= read -r payload; do + for algorithm in md5 sha1 sha256 sha512; do + openssl dgst "-${algorithm}" "${payload}" | awk '{print $NF}' >"${payload}.${algorithm}" + done +done < <(find "${repository_root}" -type f ! -name '*.md5' ! -name '*.sha1' ! -name '*.sha256' ! -name '*.sha512') + +( + cd "${staging}" + rm -f "${output}" + zip -q -r "${output}" dev +) + +echo "Built Maven Central bundle: ${output}" diff --git a/scripts/git-gpg-wrapper.sh b/scripts/git-gpg-wrapper.sh new file mode 100755 index 0000000..3c22b52 --- /dev/null +++ b/scripts/git-gpg-wrapper.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +set -euo pipefail + +: "${GPG_PASSPHRASE_FILE:?GPG_PASSPHRASE_FILE must point to a protected passphrase file}" + +exec gpg \ + --batch \ + --pinentry-mode loopback \ + --passphrase-file "${GPG_PASSPHRASE_FILE}" \ + "$@" diff --git a/scripts/verify-central-bundle.sh b/scripts/verify-central-bundle.sh new file mode 100755 index 0000000..6934647 --- /dev/null +++ b/scripts/verify-central-bundle.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 2 ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +bundle=$1 +version=$2 +project_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +inventory="${project_root}/config/release/public-artifacts.txt" + +if [[ ! -f "${bundle}" ]]; then + echo "Central bundle not found: ${bundle}" >&2 + exit 1 +fi + +staging=$(mktemp -d) +trap 'rm -rf "${staging}"' EXIT +unzip -q "${bundle}" -d "${staging}" + +repository_root="${staging}/dev/juherr/datex4j" +if [[ ! -d "${repository_root}" ]]; then + echo "Bundle does not contain dev/juherr/datex4j" >&2 + exit 1 +fi + +require_payload() { + local path=$1 + if [[ ! -f "${path}" ]]; then + echo "Missing release payload: ${path#${staging}/}" >&2 + exit 1 + fi + + if [[ ! -f "${path}.asc" ]]; then + echo "Missing signature: ${path#${staging}/}.asc" >&2 + exit 1 + fi + if ! gpg --batch --verify "${path}.asc" "${path}" >/dev/null 2>&1; then + echo "Invalid signature: ${path#${staging}/}.asc" >&2 + exit 1 + fi + + local actual_checksum checksum expected_checksum + for checksum in md5 sha1 sha256 sha512; do + if [[ ! -f "${path}.${checksum}" ]]; then + echo "Missing ${checksum} checksum: ${path#${staging}/}.${checksum}" >&2 + exit 1 + fi + expected_checksum=$(openssl dgst "-${checksum}" "${path}" | awk '{print $NF}') + actual_checksum=$(tr -d '[:space:]' <"${path}.${checksum}") + if [[ "${actual_checksum}" != "${expected_checksum}" ]]; then + echo "Invalid ${checksum} checksum: ${path#${staging}/}.${checksum}" >&2 + exit 1 + fi + done +} + +expected_artifacts=() +while read -r artifact_id packaging; do + [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue + expected_artifacts+=("${artifact_id}") + + artifact_root="${repository_root}/${artifact_id}/${version}" + base="${artifact_root}/${artifact_id}-${version}" + require_payload "${base}.pom" + + if [[ "${packaging}" == "jar" ]]; then + require_payload "${base}.jar" + require_payload "${base}-sources.jar" + require_payload "${base}-javadoc.jar" + elif [[ "${packaging}" != "pom" ]]; then + echo "Unsupported packaging '${packaging}' for ${artifact_id}" >&2 + exit 1 + fi +done < "${inventory}" + +for forbidden in datex4j-examples datex4j-consumer-tests datex4j-integration-tests; do + if [[ -d "${repository_root}/${forbidden}" ]]; then + echo "Non-publishable artifact found in bundle: ${forbidden}" >&2 + exit 1 + fi +done + +actual_artifacts=$(find "${repository_root}" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | sort) +sorted_expected=$(printf '%s\n' "${expected_artifacts[@]}" | sort) +if [[ "${actual_artifacts}" != "${sorted_expected}" ]]; then + echo "Bundle artifact inventory differs from config/release/public-artifacts.txt" >&2 + diff -u <(printf '%s\n' "${sorted_expected}") <(printf '%s\n' "${actual_artifacts}") || true + exit 1 +fi + +echo "Verified ${#expected_artifacts[@]} Maven Central artifacts for ${version}." diff --git a/scripts/verify-revapi-allowlist.sh b/scripts/verify-revapi-allowlist.sh new file mode 100755 index 0000000..26255a1 --- /dev/null +++ b/scripts/verify-revapi-allowlist.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +project_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +allowlist="${project_root}/config/revapi/accepted-differences.json" +changelog="${project_root}/CHANGELOG.md" +pom="${project_root}/pom.xml" + +if ! jq --exit-status 'type == "array"' "${allowlist}" >/dev/null; then + echo "Revapi allowlist must be a JSON array." >&2 + exit 1 +fi + +entry_count=$(jq 'length' "${allowlist}") +if [[ "${entry_count}" -eq 0 ]]; then + if ! grep -Fq 'revapi.differences' "${pom}"; then + echo "The empty Revapi allowlist requires the revapi.differences transform to be disabled." >&2 + exit 1 + fi + echo "Verified empty Revapi allowlist." + exit 0 +fi + +if grep -Fq 'revapi.differences' "${pom}"; then + echo "Enable the revapi.differences transform before accepting differences." >&2 + exit 1 +fi + +if ! jq --exit-status ' + length == 1 + and .[0].extension == "revapi.differences" + and (. [0].configuration.differences | type == "array" and length > 0) + and all( + .[0].configuration.differences[]; + .ignore == true + and (.code | type == "string" and length > 0) + and ( + (.old | type == "string" and length > 0) + or (.new | type == "string" and length > 0) + ) + and ( + .justification + | type == "string" + and startswith("Migration:") + and length > 10 + ) + ) +' "${allowlist}" >/dev/null; then + echo "Each Revapi exception must be exact, ignored explicitly, and justified with 'Migration:'." >&2 + exit 1 +fi + +while IFS= read -r justification; do + if ! grep -Fq "${justification}" "${changelog}"; then + echo "Revapi migration is missing from CHANGELOG.md: ${justification}" >&2 + exit 1 + fi +done < <(jq --raw-output '.[0].configuration.differences[].justification' "${allowlist}") + +echo "Verified Revapi compatibility exceptions." diff --git a/scripts/verify.sh b/scripts/verify.sh index 616b266..ac08dac 100755 --- a/scripts/verify.sh +++ b/scripts/verify.sh @@ -47,6 +47,7 @@ run_logged "GitHub Actions syntax" mise exec -- actionlint -color run_logged \ "GitHub Actions security" \ mise exec -- zizmor --min-severity medium .github/workflows +run_logged "Revapi allowlist policy" ./scripts/verify-revapi-allowlist.sh run_logged \ "critical XML and validation modules" \ ./mvnw --batch-mode --no-transfer-progress \