diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8cd6fb36..8d83d01a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,11 +1,12 @@ name: Build and Deploy on: + workflow_dispatch: push: branches: - - 1.x + - upgrade-netex-2.0_for_1.x pull_request: branches: - - 1.x + - upgrade-netex-2.0_for_1.x jobs: maven-verify: runs-on: ubuntu-24.04 @@ -52,7 +53,7 @@ jobs: publish-snapshot: name: Publish snapshot to Maven Central - if: github.repository_owner == 'entur' && github.event_name == 'push' && github.ref == 'refs/heads/1.x' + if: github.repository_owner == 'entur' && github.event_name == 'push' && github.ref == 'refs/heads/upgrade-netex-2.0_for_1.x' needs: maven-verify uses: ./.github/workflows/maven-jreleaser-release.yml with: diff --git a/.github/workflows/release-manual.yml b/.github/workflows/release-manual.yml index b88ea872..d9cae1de 100644 --- a/.github/workflows/release-manual.yml +++ b/.github/workflows/release-manual.yml @@ -37,8 +37,8 @@ jobs: - uses: actions/setup-java@v4 with: - java-version: 11 - distribution: liberica + java-version: 8 + distribution: temurin cache: maven - name: Install xmlstarlet diff --git a/.gitignore b/.gitignore index ddac564d..0d150fa9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,5 @@ target/ -pom.xml.tag -pom.xml.releaseBackup pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties profile .idea *.iml diff --git a/CLAUDE.md b/CLAUDE.md index 6c729cdc..dca8ff67 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,12 +4,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -**netex-java-model** is a Java library that generates JAXB model classes from NeTEx (Network Timetable Exchange) XML Schema Definition (XSD) files. The XSD files are downloaded from https://github.com/entur/NeTEx and compiled into Java classes at build time. +**netex-java-model** (Maven coordinates `org.entur:netex-java-model`, 1.x line) is a Java library that generates JAXB model classes from NeTEx (Network Timetable Exchange) XML Schema Definition (XSD) files. The NeTEx 2.0 XSD is downloaded from https://github.com/TransmodelEcosystem/NeTEx and compiled into Java classes at build time. The legacy schemas used only for validation (1.04beta - 1.16.1) are downloaded from the Entur fork at https://github.com/entur/NeTEx. + +The 1.x branch targets Java 8 and Java EE 8 (`javax.xml.bind`, JAXB 2.3); the Jakarta EE line of the library lives on `master`. - **Organization**: Entur AS (Norwegian public transport data provider) - **License**: EUPL-1.2 -- **Java Version**: 8 (builds on 8 in CI) -- **Package**: `org.rutebanken.netex.model` (generated), `org.rutebanken.netex` and `org.rutebanken.util` (source) +- **Java Version**: 8 (builds and publishes on 8 in CI) +- **Package**: `org.rutebanken.netex.model` (generated), `org.rutebanken.netex`, `org.rutebanken.netex.client`, `org.rutebanken.netex.validation` and `org.rutebanken.util` (source). GML and SIRI types are generated in `net.opengis.gml._3` and `uk.org.siri.siri`. ## Build Commands @@ -48,20 +50,21 @@ mvn validate ## Key Technical Details - **JAXB Bindings**: The `bindings.xjb` file customizes code generation, including: - - Java 8 time type adapters (`LocalDateTime`, `LocalTime`, `Duration`) + - Java 8 time type adapters (`LocalDateTime`, `LocalDate`, `LocalTime`, `Duration`) - Property name conflict resolution - Package naming (`org.rutebanken.netex.model`) + - A generated block (between `BEGIN/END generated _Dummy bindings` markers) that hides the `_Dummy` suffix of the abstract substitution-group heads: `getDayType()` instead of `getDayType_Dummy()`, `createDayType_(...)` in `ObjectFactory`. Do not edit the block by hand; rerun `bin/generate-dummy-bindings.py src/main/resources/xsd//NeTEx_publication.xsd bindings.xjb` after a schema upgrade. - **Generated Code**: Model classes are generated in `target/generated-sources/` during build. Do not manually edit generated code. -- **NeTEx Version**: Configured in `pom.xml` via `` property (currently 1.16). +- **NeTEx Version**: Configured in `pom.xml` via `` property (currently 2.0). -- **Validation**: `NeTExValidator` class provides XML schema validation against NeTEx XSD. +- **Validation**: `NeTExValidator` class provides XML schema validation against NeTEx XSD, for versions 1.04beta through 2.0. Documents declaring 1.16 are validated against the 1.16.1 schema. ## CI/CD GitHub Actions workflows in `.github/workflows/`: -- `deploy.yml`: Main build, test, Sonar scan, and snapshot publishing (triggers on `1.x` branch) +- `deploy.yml`: Main build, test, and snapshot publishing (temporarily triggered from the `upgrade-netex-2.0_for_1.x` branch until the merge into `1.x`) - Release workflows: GitFlow-based release management - Publishes to Maven Central via JReleaser diff --git a/README.md b/README.md index f2a7693c..671c395b 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,120 @@ -# netex-java-model [![CircleCI](https://circleci.com/gh/entur/netex-java-model/tree/master.svg?style=svg)](https://circleci.com/gh/entur/netex-java-model/tree/master) +# netex-java-model (1.x) -Downloads the NeTEx-XML XSD files from https://github.com/entur/NeTEx and generates java model using jaxb. +[![Build and push](https://github.com/entur/netex-java-model/actions/workflows/deploy.yml/badge.svg?branch=1.x)](https://github.com/entur/netex-java-model/actions/workflows/deploy.yml) +[![Maven Central](https://img.shields.io/maven-central/v/org.entur/netex-java-model.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/org.entur/netex-java-model) +[![Maven Snapshots](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fcentral.sonatype.com%2Frepository%2Fmaven-snapshots%2Forg%2Fentur%2Fnetex-java-model%2Fmaven-metadata.xml&label=Snapshot)](https://central.sonatype.com/repository/maven-snapshots) +[![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue.svg)](https://joinup.ec.europa.eu/software/page/eupl) +[![Java 8+](https://img.shields.io/badge/Java-8%2B-orange)](https://adoptium.net/) -You need xmlstarlet installed. +Java model classes generated from [NeTEx](https://github.com/TransmodelEcosystem/NeTEx) (Network Timetable Exchange) XML Schema Definition (XSD) files using JAXB. Maintained by [Entur AS](https://www.entur.org). -```mvn clean install``` +This is the **1.x line**, which targets Java 8 and Java EE 8 (`javax.xml.bind`). The `master` branch holds the Jakarta EE line of the library. +## Overview -## Maven central -This project is available in the central maven repository. -See https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.entur%22 +This library downloads the NeTEx XSD files from the [NeTEx repository](https://github.com/TransmodelEcosystem/NeTEx) and compiles them into JAXB-annotated Java model classes at build time. The generated classes support full marshalling and unmarshalling of NeTEx XML documents, including schema validation against multiple NeTEx versions. The legacy schemas used only for validation (1.04beta - 1.16.1) are downloaded from the [Entur NeTEx fork](https://github.com/entur/NeTEx). -# Related project +- **NeTEx version**: 2.0 (model generation), with legacy validation support back to 1.04beta +- **JAXB**: Java XML Bind 2.3 (Java EE 8, `javax.xml.bind`) +- **Generated package**: `org.rutebanken.netex.model` +- **GML types package**: `net.opengis.gml._3` +- **SIRI types package**: `uk.org.siri.siri` -See https://github.com/entur/netex-protobuf for a conversion of NeTEx xsd files to protobuf descriptors. +## Usage + +### Release (Maven Central) + +```xml + + org.entur + netex-java-model + LATEST + +``` + +See [Maven Central](https://central.sonatype.com/artifact/org.entur/netex-java-model) for the latest released version. + +### Snapshot + +Snapshots are published to the Sonatype snapshot repository. Add the repository to your `pom.xml`: + +```xml + + + sonatype-snapshots + https://central.sonatype.com/repository/maven-snapshots + + true + + + +``` + +Then add the dependency with the snapshot version: + +```xml + + org.entur + netex-java-model + LATEST-SNAPSHOT + +``` + +## Building from Source + +### Prerequisites + +- Java 8+ +- Maven 3.x +- [`xmlstarlet`](https://xmlstar.sourceforge.net/) (`apt install xmlstarlet` on Debian/Ubuntu, `brew install xmlstarlet` on macOS) + +### Build + +```bash +# Full build: downloads XSD files, generates JAXB classes, compiles, runs tests +mvn clean install + +# Run tests only +mvn test + +# Package without running tests +mvn package -DskipTests +``` + +The XSD files are downloaded from GitHub during the `generate-sources` phase and placed under `src/main/resources/xsd/`. The JAXB model is generated into `target/generated-sources/xjc/`. + +## Project Structure + +``` +├── bin/ +│ ├── netex-download-extract.sh # Downloads NeTEx XSD from GitHub +│ ├── annotation-replacer.sh # Post-processes generated Java files +│ ├── generate-dummy-bindings.py # Regenerates the bindings hiding the _Dummy suffix +│ └── version_updater.sh # Updates version references in bindings +├── bindings.xjb # JAXB customization bindings +├── src/main/java/org/rutebanken/ +│ ├── netex/ # NeTEx utilities (validation, client, toString style) +│ └── util/ # XML adapters for Java time types +├── src/main/resources/xsd/ # Downloaded NeTEx XSD files (build artifact) +└── src/test/ # JUnit 5 marshalling/unmarshalling tests +``` + +## Key Technical Details + +**JAXB customizations** (`bindings.xjb`): +- Java time type adapters: `LocalDateTime`, `LocalDate` and `LocalTime` via adapters in `org.rutebanken.util`, `Duration` via `jaxb-java-time-adapters` +- Fluent builder API (`withX()` methods) via `jaxb-fluent-api` +- Property name conflict resolution +- Package mapping to `org.rutebanken.netex.model` +- Hiding of the `_Dummy` suffix that NeTEx 2.0 puts on abstract substitution-group heads (`DayType_Dummy` is the head of `DayType`): containers expose `getDayType()` instead of `getDayType_Dummy()`, `ObjectFactory` exposes `createDayType_(...)` and the few classes generated for such heads keep a trailing underscore, as in `netex-java-model` 1.0.x. These bindings are generated by `bin/generate-dummy-bindings.py` and must be regenerated when the schema is upgraded. + +**Schema validation**: `NeTExValidator` supports validation against NeTEx versions 1.04beta through 2.0. A version is validated against its latest patch schema: documents declaring 1.16 are validated against the 1.16.1 schema, which is backward compatible with 1.16. + +## License + +Licensed under the [European Union Public Licence (EUPL) version 1.2](https://joinup.ec.europa.eu/software/page/eupl). + +## Related Projects + +- [netex-protobuf](https://github.com/entur/netex-protobuf) — NeTEx XSD files converted to protobuf descriptors +- [NeTEx (Entur fork)](https://github.com/entur/NeTEx) — legacy NeTEx XSD files used by this library for validation diff --git a/bin/generate-dummy-bindings.py b/bin/generate-dummy-bindings.py new file mode 100755 index 00000000..89f84a0a --- /dev/null +++ b/bin/generate-dummy-bindings.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +""" +Generate the JAXB bindings that hide the NeTEx "_Dummy" naming convention. + +NeTEx 2.0 declares the abstract substitution-group heads with a "_Dummy" suffix +(for example DayType_Dummy, head of DayType). Without customisation, xjc leaks the +suffix into the generated API: getDayType_Dummy(), getDayTypeRefOrDayType_Dummy(), +createDayType_Dummy(...) and classes such as MediumAccessDevice_Dummy. + +This script walks the schema files and emits one binding per leak: + + * a property rename for every element referencing a _Dummy head + (getDayType_Dummy() -> getDayType()); when the reference sits in a repeated + model group, the group itself is renamed (getDayTypeRefOrDayType()) + * a factory-method rename for every _Dummy element declaration + (createDayType_Dummy(...) -> createDayType_(...)); dropping the suffix entirely + would collide with the factory method of the concrete element + * a class rename for the _Dummy elements with an inline type and for the + _Dummy complex types (MediumAccessDevice_Dummy -> MediumAccessDevice_) + +The generated block is written into the bindings file between the BEGIN/END +markers below (and appended before the closing tag if the markers are absent). +Re-run it after upgrading the NeTEx schema: + + bin/generate-dummy-bindings.py src/main/resources/xsd/2.0/NeTEx_publication.xsd bindings.xjb + +Only the schema files reachable from the entry point through xsd:include and +xsd:import are visited; xjc rejects bindings for files outside the compilation. +""" + +import pathlib +import sys +import xml.etree.ElementTree as ET + +XS = "http://www.w3.org/2001/XMLSchema" +SUFFIX = "_Dummy" +BEGIN = "" +END = "" +PARTICLES = {"element", "group", "choice", "sequence", "all", "any"} +MODEL_GROUPS = {"choice", "sequence", "all"} + + +def tag(node): + return node.tag.split("}")[1] + + +def is_particle(node): + return tag(node) in PARTICLES + + +def repeated(node): + max_occurs = node.get("maxOccurs", "1") + return max_occurs == "unbounded" or int(max_occurs) > 1 + + +def lower_first(name): + return name[0].lower() + name[1:] + + +def label(node): + """Mirror xjc's ParticleBinder.computeLabel for the default property name.""" + kind = tag(node) + if kind == "element": + return node.get("name") or node.get("ref") + if kind == "group": + return node.get("ref") + if kind == "any": + return "any" + # xjc only uses the first three particles of a model group for its name. + return "Or".join(label(c) for c in [c for c in node if is_particle(c)][:3]) + + +def xpath_step(node, parent): + """One XPath step, with a positional predicate only when siblings share the tag.""" + siblings = [c for c in parent if c.tag == node.tag] + step = "xsd:" + tag(node) + if len(siblings) > 1: + step += "[%d]" % (siblings.index(node) + 1) + return step + + +def xpath(node, parents, schema): + """Absolute path from the named top-level component down to node.""" + chain = [] + current = node + while parents[current] is not schema: + chain.append(current) + current = parents[current] + top = current + if top.get("name") is None: + raise SystemExit("top-level component without a name: %s" % top.tag) + path = "//xsd:%s[@name = '%s']" % (tag(top), top.get("name")) + for n in reversed(chain): + path += "/" + xpath_step(n, parents[n]) + return path + + +def property_target(ref_element, parents): + """The node xjc binds to a property for this reference: the outermost repeated + model group containing it, or the element itself when there is none.""" + target = ref_element + current = parents[ref_element] + while tag(current) in MODEL_GROUPS: + if repeated(current): + target = current + current = parents[current] + return target + + +def process_schema(xsd_path): + schema = ET.parse(xsd_path).getroot() + parents = {c: p for p in schema.iter() for c in p} + properties = [] # (xpath, new name, old name) + factories = [] # (element name, new name) + classes = [] # (xpath, new name) + for node in schema.iter(): + kind = tag(node) + if kind == "element" and (node.get("ref") or "").endswith(SUFFIX): + target = property_target(node, parents) + old = lower_first(label(target)) + new = old.replace(SUFFIX, "") + properties.append((xpath(target, parents, schema), new, old)) + elif kind == "element" and (node.get("name") or "").endswith(SUFFIX) and parents[node] is schema: + name = node.get("name") + bare = name[: -len(SUFFIX)] + if any(tag(c) == "complexType" for c in node): + classes.append((xpath(node, parents, schema), bare + "_")) + else: + factories.append((name, bare + "_")) + elif kind == "complexType" and (node.get("name") or "").endswith(SUFFIX) and parents[node] is schema: + bare = node.get("name")[: -len(SUFFIX)] + classes.append((xpath(node, parents, schema), bare + "_")) + # The same repeated group is reached once per _Dummy reference it contains. + properties = list(dict.fromkeys(properties)) + return properties, factories, classes + + +def reachable_schemas(entry): + """All schema files included or imported, directly or not, from the entry point.""" + seen = set() + todo = [entry] + while todo: + path = todo.pop().resolve() + if path in seen: + continue + seen.add(path) + for node in ET.parse(path).getroot(): + if tag(node) in ("include", "import") and node.get("schemaLocation"): + todo.append(path.parent / node.get("schemaLocation")) + return sorted(seen) + + +def render(entry, bindings_path): + lines = ["\t" + BEGIN] + old_names = [] + for xsd_path in reachable_schemas(entry): + properties, factories, classes = process_schema(xsd_path) + if not (properties or factories or classes): + continue + location = "./" + xsd_path.relative_to(bindings_path.parent).as_posix() + lines.append('\t' % location) + for path, new, old in properties: + old_names.append(old) + lines.append('\t\t' % path) + lines.append('\t\t\t' % new) + lines.append("\t\t") + for name, new in factories: + lines.append("\t\t" % name) + lines.append('\t\t\t' % new) + lines.append("\t\t") + for path, new in classes: + lines.append('\t\t' % path) + lines.append('\t\t\t' % new) + lines.append("\t\t") + lines.append("\t") + lines.append("\t" + END) + return "\n".join(lines) + "\n", old_names + + +def splice(bindings_path, block): + text = bindings_path.read_text() + begin = text.find("\t" + BEGIN) + end = text.find(END) + if begin >= 0 and end >= 0: + end = text.index("\n", end) + 1 + return text[:begin] + block + text[end:] + closing = text.rstrip().rfind("") + return text[:closing].rstrip() + "\n\n" + block + "\n\n" + + +def main(): + if len(sys.argv) != 3: + raise SystemExit("usage: generate-dummy-bindings.py ") + entry = pathlib.Path(sys.argv[1]).resolve() + bindings_path = pathlib.Path(sys.argv[2]).resolve() + block, old_names = render(entry, bindings_path) + bindings_path.write_text(splice(bindings_path, block)) + # Printed so the result can be checked against the names xjc generated before. + for name in old_names: + print(name) + + +if __name__ == "__main__": + main() diff --git a/bindings.xjb b/bindings.xjb index 93024ab6..ee1f2738 100644 --- a/bindings.xjb +++ b/bindings.xjb @@ -8,21 +8,21 @@ - + - + + schemaLocation="./src/main/resources/xsd/2.0/netex_framework/netex_genericFramework/netex_organisation_version.xsd"> @@ -31,35 +31,28 @@ + schemaLocation="./src/main/resources/xsd/2.0/netex_part_3/part3_fares/netex_salesOfferPackage_version.xsd"> - - - - - - + schemaLocation="./src/main/resources/xsd/2.0/netex_part_3/part3_salesTransactions/netex_salesContract_version.xsd"> + schemaLocation="./src/main/resources/xsd/2.0/netex_part_3/part3_salesTransactions/netex_retailConsortium_version.xsd"> + schemaLocation="./src/main/resources/xsd/2.0/netex_framework/netex_responsibility/netex_version_support.xsd"> @@ -68,15 +61,685 @@ + + - - + schemaLocation="./src/main/resources/xsd/2.0/netex_part_1/part1_tacticalPlanning/netex_servicePattern_version.xsd"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index b523541b..6e2c7222 100644 --- a/pom.xml +++ b/pom.xml @@ -17,7 +17,7 @@ 4.0.0 org.entur netex-java-model - 1.0.16-SNAPSHOT + 1.1.0-SNAPSHOT netex-java-model Generates Java model from NeTEx xsds using jaxb. @@ -53,13 +53,6 @@ https://github.com/entur/netex-java-model/tree/master HEAD - - - snapshots - entur2-snapshots - https://entur2.jfrog.io/entur2/libs-snapshot-local - - @@ -67,9 +60,10 @@ 1.8 NeTEx - master - https://github.com/entur/${netexRepoName} - 1.16 + v2.0 + https://github.com/TransmodelEcosystem/${netexRepoName} + https://github.com/entur/${netexRepoName} + 2.0 2.3.3 @@ -90,14 +84,10 @@ 5.9.1 - 1.6.13 1.6.0 2.22.2 3.2.1 3.3.1 - 2.5.3 - 1.6 - 3.1.2 3.8.1 3.2.0 @@ -114,10 +104,6 @@ - - gpg - ${env.GPG_KEY_NAME} - @@ -139,12 +125,6 @@ ${glassfish-jaxb.version} runtime - - org.apache.cxf.xjc-utils - cxf-xjc-runtime - ${cxf-xjc.version} - runtime - @@ -186,15 +166,6 @@ - - - - org.apache.maven.wagon - wagon-ssh-external - 2.10 - - @@ -204,6 +175,14 @@ maven-clean-plugin ${maven-clean-plugin.version} + + + + + src/main/resources/xsd + + + maven-install-plugin @@ -272,9 +251,9 @@ - ${netexGithubUrl}/archive/${netexBranch}.zip + ${cenNetexGithubUrl}/archive/${netexBranch}.zip src/main/resources/xsd/${netexVersion} - NeTEx-${netexBranch}/xsd/* + NeTEx-${netexVersion}/xsd/* ./bin/netex-download-extract.sh @@ -294,6 +273,22 @@ + + + download-extract-legacy-1.16 + generate-resources + + exec + + + + ${enturNetexGithubUrl}/archive/tags/v1.0.16.1.zip + src/main/resources/xsd/1.16.1 + NeTEx-tags-v1.0.16.1/xsd/* + + ./bin/netex-download-extract.sh + + download-extract-legacy-1.15 @@ -303,7 +298,7 @@ - ${netexGithubUrl}/archive/tags/v1.0.15.zip + ${enturNetexGithubUrl}/archive/tags/v1.0.15.zip src/main/resources/xsd/1.15 NeTEx-tags-v1.0.15/xsd/* @@ -319,7 +314,7 @@ - ${netexGithubUrl}/archive/tags/v1.0.14.zip + ${enturNetexGithubUrl}/archive/tags/v1.0.14.zip src/main/resources/xsd/1.14 NeTEx-tags-v1.0.14/xsd/* @@ -335,7 +330,7 @@ - ${netexGithubUrl}/archive/tags/v1.0.13.zip + ${enturNetexGithubUrl}/archive/tags/v1.0.13.zip src/main/resources/xsd/1.13 NeTEx-tags-v1.0.13/xsd/* @@ -351,7 +346,7 @@ - ${netexGithubUrl}/archive/tags/v1.0.12.zip + ${enturNetexGithubUrl}/archive/tags/v1.0.12.zip src/main/resources/xsd/1.12 NeTEx-tags-v1.0.12/xsd/* @@ -367,7 +362,7 @@ - ${netexGithubUrl}/archive/tags/v1.0.11.zip + ${enturNetexGithubUrl}/archive/tags/v1.0.11.zip src/main/resources/xsd/1.11 NeTEx-tags-v1.0.11/xsd/* @@ -383,7 +378,7 @@ - ${netexGithubUrl}/archive/tags/v1.0.10-entur.zip + ${enturNetexGithubUrl}/archive/tags/v1.0.10-entur.zip src/main/resources/xsd/1.10 NeTEx-tags-v1.0.10-entur/xsd/* @@ -399,7 +394,7 @@ - ${netexGithubUrl}/archive/tags/v1.0.9.zip + ${enturNetexGithubUrl}/archive/tags/v1.0.9.zip src/main/resources/xsd/1.09 NeTEx-tags-v1.0.9/xsd/* @@ -415,7 +410,7 @@ - ${netexGithubUrl}/archive/tags/v1.0.8.zip + ${enturNetexGithubUrl}/archive/tags/v1.0.8.zip src/main/resources/xsd/1.08 NeTEx-tags-v1.0.8/xsd/* @@ -549,24 +544,6 @@ - - org.apache.maven.plugins - maven-release-plugin - ${maven-release-plugin.version} - - true - false - release,sign,!noPublicDeploy - deploy - - - - org.apache.maven.scm - maven-scm-provider-gitexe - 1.9.2 - - - org.apache.maven.plugins maven-enforcer-plugin @@ -633,106 +610,6 @@ - - sign - - false - - - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - false - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven-surefire-plugin.version} - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven-gpg-plugin.version} - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - ${maven-source-plugin.version} - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven-javadoc-plugin.version} - - none - - - - attach-javadocs - - jar - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} - true - - ossrh - https://oss.sonatype.org/ - - false - - - - - - - - release - - false - - - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - sonar diff --git a/renovate.json b/renovate.json index f45d8f11..11f1b6d7 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,21 @@ { "extends": [ "config:base" + ], + "packageRules": [ + { + "description": "Pin CXF XJC to 3.x - 4.x generates Jakarta XML Binding (jakarta.xml.bind) code, this branch targets Java EE 8 (javax.xml.bind)", + "allowedVersions": "<4.0.0", + "matchPackageNames": [ + "/^org\\.apache\\.cxf/" + ] + }, + { + "description": "Pin JUnit to 5.x - JUnit 6 requires Java 17, this branch targets Java 8", + "allowedVersions": "<6.0.0", + "matchPackageNames": [ + "/^org\\.junit/" + ] + } ] } diff --git a/src/main/java/org/rutebanken/netex/OmitNullsToStringStyle.java b/src/main/java/org/rutebanken/netex/OmitNullsToStringStyle.java index 6148a1e7..33047352 100644 --- a/src/main/java/org/rutebanken/netex/OmitNullsToStringStyle.java +++ b/src/main/java/org/rutebanken/netex/OmitNullsToStringStyle.java @@ -17,15 +17,12 @@ import org.apache.commons.lang3.builder.ToStringStyle; -public class OmitNullsToStringStyle extends org.apache.commons.lang3.builder.ToStringStyle { +public final class OmitNullsToStringStyle extends ToStringStyle { - /** - * - */ private static final long serialVersionUID = 7043613680114711384L; public static final ToStringStyle INSTANCE = new OmitNullsToStringStyle(); - public OmitNullsToStringStyle() { + private OmitNullsToStringStyle() { this.setUseClassName(false); this.setUseIdentityHashCode(false); this.setUseFieldNames(true); diff --git a/src/main/java/org/rutebanken/netex/client/PublicationDeliveryClient.java b/src/main/java/org/rutebanken/netex/client/PublicationDeliveryClient.java index a06ff764..0b515255 100644 --- a/src/main/java/org/rutebanken/netex/client/PublicationDeliveryClient.java +++ b/src/main/java/org/rutebanken/netex/client/PublicationDeliveryClient.java @@ -51,7 +51,7 @@ public PublicationDeliveryClient(String publicationDeliveryUrl, boolean validate this.jaxbContext = JAXBContext.newInstance(PublicationDeliveryStructure.class); this.validateAgainstSchema = validateAgainstSchema; if(validateAgainstSchema) { - neTExValidator = new NeTExValidator(); + neTExValidator = NeTExValidator.getNeTExValidator(); } } diff --git a/src/main/java/org/rutebanken/netex/validation/NeTExValidator.java b/src/main/java/org/rutebanken/netex/validation/NeTExValidator.java index 4b3f4a83..8139f2ee 100644 --- a/src/main/java/org/rutebanken/netex/validation/NeTExValidator.java +++ b/src/main/java/org/rutebanken/netex/validation/NeTExValidator.java @@ -44,22 +44,37 @@ public enum NetexVersion { v1_13 ("1.13"), v1_14 ("1.14"), v1_15 ("1.15"), - v1_16 ("1.16"); - + /** + * NeTEx 1.16, validated against the latest 1.16 patch schema. Patch releases of the schema + * are backward compatible, and documents only ever declare the minor version, so a document + * declaring 1.16 is validated against the 1.16.1 schema. + */ + v1_16 ("1.16", "1.16.1"), + v2_0 ("2.0"); + + private final String version; private final String folderName; - NetexVersion(String folderName) { + NetexVersion(String version) { + this(version, version); + } + + NetexVersion(String version, String folderName) { + this.version = version; this.folderName = folderName; } + /** + * @return the NeTEx version, which is not always the name of the folder holding its schema. + */ @Override public String toString() { - return folderName; + return version; } } private final Schema neTExSchema; - public static final NetexVersion LATEST = NetexVersion.v1_16; + public static final NetexVersion LATEST = NetexVersion.v2_0; private static final Map VALIDATORS_PER_VERSION = new EnumMap<>(NetexVersion.class); @@ -90,19 +105,13 @@ public static NeTExValidator getNeTExValidator() throws IOException, SAXExceptio /** - * Use static getNeTExValidator to avoid initializing more schemas than needed. - */ - public NeTExValidator() throws IOException, SAXException { - this(LATEST); - } - - /** - * Use static getNeTExValidator to avoid initializing more schemas than needed. + * Validators are cached per version; use {@link #getNeTExValidator(NetexVersion)} or + * {@link #getNeTExValidator()}. */ - public NeTExValidator(NetexVersion version) throws IOException, SAXException { + private NeTExValidator(NetexVersion version) throws IOException, SAXException { SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - String resourceName = "xsd/"+ version +"/NeTEx_publication.xsd"; + String resourceName = "xsd/"+ version.folderName +"/NeTEx_publication.xsd"; LOGGER.info("Loading resource: {}", resourceName); URL resource = getClass().getClassLoader().getResource(resourceName); if(resource == null) { diff --git a/src/main/java/org/rutebanken/util/LocalDateTimeISO8601XmlAdapter.java b/src/main/java/org/rutebanken/util/LocalDateTimeISO8601XmlAdapter.java index 36fbc05b..e2c067dd 100644 --- a/src/main/java/org/rutebanken/util/LocalDateTimeISO8601XmlAdapter.java +++ b/src/main/java/org/rutebanken/util/LocalDateTimeISO8601XmlAdapter.java @@ -17,20 +17,20 @@ import javax.xml.bind.annotation.adapters.XmlAdapter; import java.time.LocalDateTime; -import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoField; +/** + * Maps xs:dateTime to {@link LocalDateTime}. An optional trailing zone offset or UTC designator is accepted + * and ignored when unmarshalling, so the local date-time is taken as written in the document. + */ public class LocalDateTimeISO8601XmlAdapter extends XmlAdapter { private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd'T'HH:mm:ss") .optionalStart().appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true).optionalEnd() - .optionalStart().appendPattern("XXXXX") - .optionalEnd() - -// - .parseDefaulting(ChronoField.OFFSET_SECONDS,OffsetDateTime.now().getLong(ChronoField.OFFSET_SECONDS) ).toFormatter(); + .optionalStart().appendPattern("XXXXX").optionalEnd() + .toFormatter(); @Override public LocalDateTime unmarshal(String inputDate) { diff --git a/src/main/java/org/rutebanken/util/LocalDateXmlAdapter.java b/src/main/java/org/rutebanken/util/LocalDateXmlAdapter.java index 359c214f..8429131d 100644 --- a/src/main/java/org/rutebanken/util/LocalDateXmlAdapter.java +++ b/src/main/java/org/rutebanken/util/LocalDateXmlAdapter.java @@ -16,30 +16,31 @@ package org.rutebanken.util; import javax.xml.bind.annotation.adapters.XmlAdapter; -import java.time.LocalDateTime; -import java.time.OffsetDateTime; +import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; -import java.time.temporal.ChronoField; -public class LocalDateXmlAdapter extends XmlAdapter { - private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd") +/** + * Maps xs:date to {@link LocalDate}. An optional trailing zone offset ({@code 2026-05-03+02:00}, + * {@code 2026-05-03Z}), as permitted by xs:date, is accepted and ignored when unmarshalling. Dates + * are always marshalled as {@code yyyy-MM-dd}. + */ +public class LocalDateXmlAdapter extends XmlAdapter { + + private static final DateTimeFormatter PARSER = new DateTimeFormatterBuilder() + .append(DateTimeFormatter.ISO_LOCAL_DATE) .optionalStart().appendPattern("XXXXX").optionalEnd() - .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) - .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) - .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) - .parseDefaulting(ChronoField.OFFSET_SECONDS,OffsetDateTime.now().getLong(ChronoField.OFFSET_SECONDS) ) - .toFormatter(); + .toFormatter(); @Override - public LocalDateTime unmarshal(String inputDate) { - return LocalDateTime.parse(inputDate, formatter); + public LocalDate unmarshal(String inputDate) { + return LocalDate.parse(inputDate, PARSER); } @Override - public String marshal(LocalDateTime inputDate) { - if(inputDate != null) { - return formatter.format(inputDate); + public String marshal(LocalDate inputDate) { + if (inputDate != null) { + return DateTimeFormatter.ISO_LOCAL_DATE.format(inputDate); } else { return null; } diff --git a/src/main/java/org/rutebanken/util/LocalTimeISO8601XmlAdapter.java b/src/main/java/org/rutebanken/util/LocalTimeISO8601XmlAdapter.java index 35372428..fdf3780e 100644 --- a/src/main/java/org/rutebanken/util/LocalTimeISO8601XmlAdapter.java +++ b/src/main/java/org/rutebanken/util/LocalTimeISO8601XmlAdapter.java @@ -17,20 +17,20 @@ import javax.xml.bind.annotation.adapters.XmlAdapter; import java.time.LocalTime; -import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.temporal.ChronoField; +/** + * Maps xs:time to {@link LocalTime}. An optional trailing zone offset or UTC designator is accepted + * and ignored when unmarshalling, so the local time is taken as written in the document. + */ public class LocalTimeISO8601XmlAdapter extends XmlAdapter { private static final DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendPattern("HH:mm:ss") .optionalStart().appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true).optionalEnd() - .optionalStart().appendPattern("XXXXX") - .optionalEnd() - -// - .parseDefaulting(ChronoField.OFFSET_SECONDS,OffsetDateTime.now().getLong(ChronoField.OFFSET_SECONDS) ).toFormatter(); + .optionalStart().appendPattern("XXXXX").optionalEnd() + .toFormatter(); @Override public LocalTime unmarshal(String inputDate) { diff --git a/src/test/java/org/rutebanken/netex/model/AbstractUnmarshalFrameTest.java b/src/test/java/org/rutebanken/netex/model/AbstractUnmarshalFrameTest.java index e3fddbc6..e7bb2ee2 100644 --- a/src/test/java/org/rutebanken/netex/model/AbstractUnmarshalFrameTest.java +++ b/src/test/java/org/rutebanken/netex/model/AbstractUnmarshalFrameTest.java @@ -32,6 +32,19 @@ public static void initContext() throws JAXBException { } - + /** + * Helper method to extract string value from MultilingualString. + * In NeTEx 2.0, MultilingualString uses mixed content instead of a simple value. + */ + protected static String getStringValue(MultilingualString multilingualString) { + if (multilingualString == null || multilingualString.getContent() == null) { + return null; + } + return multilingualString.getContent().stream() + .filter(String.class::isInstance) + .map(String.class::cast) + .findFirst() + .orElse(null); + } } diff --git a/src/test/java/org/rutebanken/netex/model/MarshalUnmarshalTest.java b/src/test/java/org/rutebanken/netex/model/MarshalUnmarshalTest.java index 78b8b7bc..ce4cc79e 100644 --- a/src/test/java/org/rutebanken/netex/model/MarshalUnmarshalTest.java +++ b/src/test/java/org/rutebanken/netex/model/MarshalUnmarshalTest.java @@ -25,13 +25,15 @@ import javax.xml.bind.Unmarshaller; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamWriter; -import java.io.*; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.temporal.ChronoField; import java.util.Arrays; import java.util.List; @@ -55,7 +57,7 @@ void publicationDeliveryWithOffsetDateTime() throws JAXBException { Marshaller marshaller = jaxbContext.createMarshaller(); PublicationDeliveryStructure publicationDelivery = new PublicationDeliveryStructure() - .withDescription(new MultilingualString().withValue("value").withLang("no").withTextIdType("")).withPublicationTimestamp(LocalDateTime.now().withNano(0)) + .withDescription(new MultilingualString().withContent("value").withLang("no").withTextIdType("")).withPublicationTimestamp(LocalDateTime.now().withNano(0)) .withParticipantRef("participantRef"); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); @@ -75,7 +77,7 @@ void publicationDeliveryWithOffsetDateTime() throws JAXBException { System.out.println(actual.getPublicationTimestamp()); assertThat(actual.getPublicationTimestamp()).isEqualTo(publicationDelivery.getPublicationTimestamp()); assertThat(actual.getDescription()).isNotNull(); - assertThat(actual.getDescription().getValue()).isEqualTo(publicationDelivery.getDescription().getValue()); + assertThat(actual.getDescription().getContent()).isEqualTo(publicationDelivery.getDescription().getContent()); assertThat(actual.getParticipantRef()).isEqualTo(publicationDelivery.getParticipantRef()); } @@ -134,7 +136,7 @@ void timetableWithVehicleModes() throws JAXBException { Marshaller marshaller = jaxbContext.createMarshaller(); TimetableFrame timetableFrame = factory.createTimetableFrame().withVersion("any").withId("TimetableFrame") - .withName(factory.createMultilingualString().withValue("TimetableFrame")).withVehicleModes(VehicleModeEnumeration.AIR); + .withName(factory.createMultilingualString().withContent("TimetableFrame")).withVehicleModes(AllPublicTransportModesEnumeration.AIR); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); @@ -151,9 +153,9 @@ void timetableWithVehicleModes() throws JAXBException { assertThat(actual.getVersion()).isNotNull().isNotEmpty().isEqualTo(timetableFrame.getVersion()); assertThat(actual.getId()).isNotNull().isNotEmpty().isEqualTo(timetableFrame.getId()); - assertThat(actual.getName().getValue()).isNotNull().isNotEmpty().isEqualTo(timetableFrame.getName().getValue()); + assertThat(actual.getName().getContent()).isNotNull().isNotEmpty().isEqualTo(timetableFrame.getName().getContent()); assertThat(actual.getVehicleModes()).isNotNull().isNotEmpty().hasSize(1); - assertThat(actual.getVehicleModes().get(0)).isNotNull().isEqualTo(VehicleModeEnumeration.AIR); + assertThat(actual.getVehicleModes().get(0)).isNotNull().isEqualTo(AllPublicTransportModesEnumeration.AIR); } @Test @@ -162,11 +164,11 @@ void dayTypeWithPropertiesOfDay() throws JAXBException { List daysOfWeek = Arrays.asList(DayOfWeekEnumeration.MONDAY, DayOfWeekEnumeration.TUESDAY, DayOfWeekEnumeration.WEDNESDAY, DayOfWeekEnumeration.THURSDAY, DayOfWeekEnumeration.FRIDAY); - PropertyOfDay propertyOfDay = factory.createPropertyOfDay().withDescription(factory.createMultilingualString().withValue("PropertyOfDay")) - .withName(factory.createMultilingualString().withValue("PropertyOfDay")).withDaysOfWeek(daysOfWeek); + PropertyOfDay propertyOfDay = factory.createPropertyOfDay().withDescription(factory.createMultilingualString().withContent("PropertyOfDay")) + .withName(factory.createMultilingualString().withContent("PropertyOfDay")).withDaysOfWeek(daysOfWeek); PropertiesOfDay_RelStructure propertiesOfDay = factory.createPropertiesOfDay_RelStructure().withPropertyOfDay(propertyOfDay); DayType dayType = factory.createDayType().withVersion("any").withId(String.format("%s:dt:weekday", "SK4488")) - .withName(factory.createMultilingualString().withValue("Ukedager (mandag til fredag)")).withProperties(propertiesOfDay); + .withName(factory.createMultilingualString().withContent("Ukedager (mandag til fredag)")).withProperties(propertiesOfDay); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); @@ -183,73 +185,80 @@ void dayTypeWithPropertiesOfDay() throws JAXBException { assertThat(actual.getVersion()).isNotNull().isNotEmpty().isEqualTo(dayType.getVersion()); assertThat(actual.getId()).isNotNull().isNotEmpty().isEqualTo(dayType.getId()); - assertThat(actual.getName().getValue()).isNotNull().isNotEmpty().isEqualTo(dayType.getName().getValue()); + assertThat(actual.getName().getContent()).isNotNull().isNotEmpty().isEqualTo(dayType.getName().getContent()); assertThat(actual.getProperties().getPropertyOfDay().get(0).getDaysOfWeek()).isNotNull().isNotEmpty().hasSize(5) .hasOnlyElementsOfType(DayOfWeekEnumeration.class).hasSameElementsAs(daysOfWeek); } @Test - void datedCallWithLocalDate() throws JAXBException { + void marshalledNamespacePrefixes() throws JAXBException { Marshaller marshaller = jaxbContext.createMarshaller(); - DatedCall datedCall = new DatedCall().withArrivalDate(LocalDateTime.now().with(ChronoField.MILLI_OF_DAY, 0)); + PublicationDeliveryStructure publicationDeliveryStructure = new PublicationDeliveryStructure(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - marshaller.marshal(factory.createDatedCall(datedCall), byteArrayOutputStream); + marshaller.marshal(factory.createPublicationDelivery(publicationDeliveryStructure), byteArrayOutputStream); String xml = byteArrayOutputStream.toString(); System.out.println(xml); - Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); - - JAXBElement actual = (JAXBElement) unmarshaller.unmarshal(new ByteArrayInputStream(xml.getBytes())); - - assertThat(actual.getValue().getArrivalDate()).isEqualTo(datedCall.getArrivalDate()); - + assertThat(xml) + .as("Namespace declaration without prefix for netex").contains("xmlns=\"http://www.netex.org.uk/netex\"") + .as(" actual = (JAXBElement) unmarshaller.unmarshal(new ByteArrayInputStream(xml.getBytes())); + + assertThat(actual.getValue().getFromDate().getHour()).isEqualTo(condition.getFromDate().getHour()); + assertThat(actual.getValue().getFromDate()).isEqualToIgnoringNanos(condition.getFromDate()); } @Test - void datedCallWithLocalDateTime() throws JAXBException { + void availabilityConditionWithCreatedLocalDateTime() throws JAXBException { Marshaller marshaller = jaxbContext.createMarshaller(); - DatedCall datedCall = new DatedCall().withChanged(LocalDateTime.now()); + AvailabilityCondition condition = new AvailabilityCondition() + .withCreated(LocalDateTime.now()) + .withId("test:condition:2") + .withVersion("1"); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - marshaller.marshal(factory.createDatedCall(datedCall), byteArrayOutputStream); + marshaller.marshal(factory.createAvailabilityCondition(condition), byteArrayOutputStream); String xml = byteArrayOutputStream.toString(); System.out.println(xml); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); - JAXBElement actual = (JAXBElement) unmarshaller.unmarshal(new ByteArrayInputStream(xml.getBytes())); + @SuppressWarnings("unchecked") + JAXBElement actual = (JAXBElement) unmarshaller.unmarshal(new ByteArrayInputStream(xml.getBytes())); - assertThat(actual.getValue().getChanged().getHour()).isEqualTo(datedCall.getChanged().getHour()); - assertThat(actual.getValue().getChanged()).isEqualToIgnoringNanos(datedCall.getChanged()); + assertThat(actual.getValue().getCreated().getHour()).isEqualTo(condition.getCreated().getHour()); + assertThat(actual.getValue().getCreated()).isEqualToIgnoringNanos(condition.getCreated()); } @Test @@ -264,12 +273,12 @@ void unmarshalPublicationDeliveryAndVerifyValidBetween() throws JAXBException, I CompositeFrame compositeFrame = (CompositeFrame) actual.dataObjects.compositeFrameOrCommonFrame .get(0).getValue(); ValidityConditions_RelStructure validityConditions = compositeFrame.getValidityConditions(); - ValidBetween validBetweenWithTimezone = (ValidBetween) validityConditions.getValidityConditionRefOrValidBetweenOrValidityCondition_().get(0); + ValidBetween validBetweenWithTimezone = (ValidBetween) validityConditions.getValidityConditionRefOrValidBetweenOrValidityCondition().get(0); assertThat(validBetweenWithTimezone.getFromDate()).isNotNull(); assertThat(validBetweenWithTimezone.getToDate()).isNotNull(); assertThat(validBetweenWithTimezone.getToDate()).hasToString("2017-01-01T11:00"); - ValidBetween validBetweenWithoutTimezone = (ValidBetween) validityConditions.getValidityConditionRefOrValidBetweenOrValidityCondition_().get(1); + ValidBetween validBetweenWithoutTimezone = (ValidBetween) validityConditions.getValidityConditionRefOrValidBetweenOrValidityCondition().get(1); assertThat(validBetweenWithoutTimezone.getFromDate()).isNotNull(); assertThat(validBetweenWithoutTimezone.getToDate()).isNotNull(); diff --git a/src/test/java/org/rutebanken/netex/model/ToStringTest.java b/src/test/java/org/rutebanken/netex/model/ToStringTest.java index 895f5677..a2e253d8 100644 --- a/src/test/java/org/rutebanken/netex/model/ToStringTest.java +++ b/src/test/java/org/rutebanken/netex/model/ToStringTest.java @@ -23,8 +23,8 @@ class ToStringTest { @Test void toStringMethod() { StopPlace stopPlace = new StopPlace() - .withName(new MultilingualString().withValue("berger")) - .withDescription(new MultilingualString().withValue("description")); + .withName(new MultilingualString().withContent("berger")) + .withDescription(new MultilingualString().withContent("description")); System.out.println(stopPlace.toString()); assertThat(stopPlace.toString()).contains("berger"); assertThat(stopPlace.toString()).contains("description"); diff --git a/src/test/java/org/rutebanken/netex/model/UnmarshalResourceFrameTest.java b/src/test/java/org/rutebanken/netex/model/UnmarshalResourceFrameTest.java index 67e2324a..a84b0129 100644 --- a/src/test/java/org/rutebanken/netex/model/UnmarshalResourceFrameTest.java +++ b/src/test/java/org/rutebanken/netex/model/UnmarshalResourceFrameTest.java @@ -110,8 +110,8 @@ void unmarshalResourceFrame() throws JAXBException { CompositeFrame compositeFrame = (CompositeFrame) publicationDeliveryStructure.getDataObjects().getCompositeFrameOrCommonFrame().get(0).getValue(); ResourceFrame resourceFrame = (ResourceFrame) compositeFrame.getFrames().getCommonFrame().get(0).getValue(); - Authority authority = (Authority) resourceFrame.getOrganisations().getOrganisation_().get(0).getValue(); - assertEquals("Vy", authority.getName().getValue()); + Authority authority = (Authority) resourceFrame.getOrganisations().getOrganisation().get(0).getValue(); + assertEquals("Vy", getStringValue(authority.getName())); diff --git a/src/test/java/org/rutebanken/netex/model/UnmarshalServiceCalendarFrameTest.java b/src/test/java/org/rutebanken/netex/model/UnmarshalServiceCalendarFrameTest.java index b469cff6..1a0b4ecc 100644 --- a/src/test/java/org/rutebanken/netex/model/UnmarshalServiceCalendarFrameTest.java +++ b/src/test/java/org/rutebanken/netex/model/UnmarshalServiceCalendarFrameTest.java @@ -21,6 +21,7 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import java.io.ByteArrayInputStream; +import java.time.LocalDate; import java.time.LocalDateTime; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -128,17 +129,17 @@ void unmarshalServiceCalendarFrame() throws JAXBException { ServiceCalendarFrame serviceCalendarFrame = (ServiceCalendarFrame) compositeFrame.getFrames().getCommonFrame().get(0).getValue(); - DayType dayType = (DayType) serviceCalendarFrame.getDayTypes().getDayType_().get(0).getValue(); + DayType dayType = (DayType) serviceCalendarFrame.getDayTypes().getDayType().get(0).getValue(); assertEquals(DayOfWeekEnumeration.TUESDAY, dayType.getProperties().getPropertyOfDay().get(0).getDaysOfWeek().get(0)); OperatingDay operatingDay = serviceCalendarFrame.getOperatingDays().getOperatingDay().get(0); - assertEquals(LocalDateTime.of(2023,10,29,0,0), operatingDay.getCalendarDate()); + assertEquals(LocalDate.of(2023, 10, 29), operatingDay.getCalendarDate()); OperatingPeriod operatingPeriod = (OperatingPeriod) serviceCalendarFrame.getOperatingPeriods().getOperatingPeriodOrUicOperatingPeriod().get(0); assertEquals(LocalDateTime.of(2023,8,17,0,0), operatingPeriod.getFromDate()); DayTypeAssignment dayTypeAssignment = serviceCalendarFrame.getDayTypeAssignments().getDayTypeAssignment().get(0); - assertEquals(LocalDateTime.of(2024,5,1,0,0), dayTypeAssignment.getDate()); + assertEquals(LocalDate.of(2024, 5, 1), dayTypeAssignment.getDate()); } diff --git a/src/test/java/org/rutebanken/netex/model/UnmarshalServiceFrameTest.java b/src/test/java/org/rutebanken/netex/model/UnmarshalServiceFrameTest.java index 26c385e7..0beb8c0f 100644 --- a/src/test/java/org/rutebanken/netex/model/UnmarshalServiceFrameTest.java +++ b/src/test/java/org/rutebanken/netex/model/UnmarshalServiceFrameTest.java @@ -146,21 +146,21 @@ void unmarshalServiceFrame() throws JAXBException { CompositeFrame compositeFrame = (CompositeFrame) publicationDeliveryStructure.getDataObjects().getCompositeFrameOrCommonFrame().get(0).getValue(); ServiceFrame serviceFrame = (ServiceFrame) compositeFrame.getFrames().getCommonFrame().get(0).getValue(); - Line line = (Line) serviceFrame.getLines().getLine_().get(0).getValue(); - assertEquals("Narvik-Riksgränsen (Sverige)", line.getName().getValue()); - assertEquals("F8", line.getPublicCode()); + Line line = (Line) serviceFrame.getLines().getLine().get(0).getValue(); + assertEquals("Narvik-Riksgränsen (Sverige)", getStringValue(line.getName())); + assertEquals("F8", line.getPublicCode().getValue()); assertEquals("F8", line.getPrivateCode().getValue()); assertEquals("rail", line.getTransportMode().value()); - Route route = (Route) serviceFrame.getRoutes().getRoute_().get(0).getValue(); - assertEquals("Riksgränsen-Narvik Ofotbanen", route.getName().getValue()); - assertEquals("KJ-NK", route.getShortName().getValue()); + Route route = (Route) serviceFrame.getRoutes().getRoute().get(0).getValue(); + assertEquals("Riksgränsen-Narvik Ofotbanen", getStringValue(route.getName())); + assertEquals("KJ-NK", getStringValue(route.getShortName())); PointOnRoute pointOnRoute = route.getPointsInSequence().getPointOnRoute().get(0); assertEquals("VYG:RoutePoint:KJ", pointOnRoute.getPointRef().getValue().getRef()); - JourneyPattern journeyPattern = (JourneyPattern) serviceFrame.getJourneyPatterns().getJourneyPattern_OrJourneyPatternView().get(0).getValue(); - assertEquals("KMB-NK", journeyPattern.getName().getValue()); + JourneyPattern journeyPattern = (JourneyPattern) serviceFrame.getJourneyPatterns().getJourneyPattern().get(0).getValue(); + assertEquals("KMB-NK", getStringValue(journeyPattern.getName())); assertEquals("VYG:Route:F8-R", journeyPattern.getRouteRef().getRef()); StopPointInJourneyPattern stopPointInJourneyPattern = (StopPointInJourneyPattern) journeyPattern.getPointsInSequence().getPointInJourneyPatternOrStopPointInJourneyPatternOrTimingPointInJourneyPattern().get(0); diff --git a/src/test/java/org/rutebanken/netex/model/UnmarshalSiteFrameTest.java b/src/test/java/org/rutebanken/netex/model/UnmarshalSiteFrameTest.java index 125f0f9e..d6b3e093 100644 --- a/src/test/java/org/rutebanken/netex/model/UnmarshalSiteFrameTest.java +++ b/src/test/java/org/rutebanken/netex/model/UnmarshalSiteFrameTest.java @@ -157,19 +157,19 @@ void unmarshalSiteFrame() throws JAXBException { PublicationDeliveryStructure publicationDeliveryStructure = jaxbElement.getValue(); SiteFrame siteFrame = (SiteFrame) publicationDeliveryStructure.getDataObjects().getCompositeFrameOrCommonFrame().get(0).getValue(); - StopPlace stopPlace = (StopPlace) siteFrame.getStopPlaces().getStopPlace_().get(0).getValue(); - assertEquals("Krokstien", stopPlace.getName().getValue()); - assertEquals(AllVehicleModesOfTransportEnumeration.BUS, stopPlace.getTransportMode()); + StopPlace stopPlace = (StopPlace) siteFrame.getStopPlaces().getStopPlace().get(0); + assertEquals("Krokstien", getStringValue(stopPlace.getName())); + assertEquals(AllPublicTransportModesEnumeration.BUS, stopPlace.getTransportMode()); assertEquals(StopTypeEnumeration.ONSTREET_BUS, stopPlace.getStopPlaceType()); - assertEquals("BRA:TariffZone:311", stopPlace.getTariffZones().getTariffZoneRef_().get(0).getValue().getRef()); + assertEquals("BRA:TariffZone:311", stopPlace.getTariffZones().getTariffZoneRef().get(0).getValue().getRef()); Quay quay = (Quay) stopPlace.getQuays().getQuayRefOrQuay().get(0).getValue(); assertEquals(BigDecimal.valueOf(59.910579), quay.getCentroid().getLocation().getLatitude()); TariffZone tariffZone = (TariffZone) siteFrame.getTariffZones().getTariffZone().get(0).getValue(); - assertEquals("1", tariffZone.getName().getValue()); + assertEquals("1", getStringValue(tariffZone.getName())); GroupOfStopPlaces groupOfStopPlaces = siteFrame.getGroupsOfStopPlaces().getGroupOfStopPlaces().get(0); - assertEquals("Lillehammer", groupOfStopPlaces.getName().getValue()); + assertEquals("Lillehammer", getStringValue(groupOfStopPlaces.getName())); assertEquals("NSR:StopPlace:420", groupOfStopPlaces.getMembers().getStopPlaceRef().get(0).getValue().getRef()); } @@ -211,7 +211,7 @@ void unmarshalTopographicPlaceWithPolygon() throws JAXBException { SiteFrame siteFrame = (SiteFrame) publicationDeliveryStructure.getDataObjects().getCompositeFrameOrCommonFrame().get(0).getValue(); TopographicPlace topographicPlace = siteFrame.getTopographicPlaces().getTopographicPlace().get(0); - assertEquals("Oslo", topographicPlace.getDescriptor().getName().getValue()); + assertEquals("Oslo", getStringValue(topographicPlace.getDescriptor().getName())); assertEquals(TopographicPlaceTypeEnumeration.MUNICIPALITY, topographicPlace.getTopographicPlaceType()); assertNotNull(topographicPlace.getPolygon()); } diff --git a/src/test/java/org/rutebanken/netex/model/UnmarshalTimetableFrameTest.java b/src/test/java/org/rutebanken/netex/model/UnmarshalTimetableFrameTest.java index 163fd7a1..4bf92ac7 100644 --- a/src/test/java/org/rutebanken/netex/model/UnmarshalTimetableFrameTest.java +++ b/src/test/java/org/rutebanken/netex/model/UnmarshalTimetableFrameTest.java @@ -120,7 +120,7 @@ void unmarshalTimetableFrame() throws JAXBException { TimetableFrame timetableFrame = (TimetableFrame) compositeFrame.getFrames().getCommonFrame().get(0).getValue(); ServiceJourney serviceJourney = (ServiceJourney) timetableFrame.getVehicleJourneys().getVehicleJourneyOrDatedVehicleJourneyOrNormalDatedVehicleJourney().get(0); - assertEquals("96", serviceJourney.getName().getValue()); + assertEquals("96", getStringValue(serviceJourney.getName())); assertEquals("96", serviceJourney.getPrivateCode().getValue()); assertEquals("rail", serviceJourney.getTransportMode().value()); diff --git a/src/test/java/org/rutebanken/netex/validation/NeTExValidatorTest.java b/src/test/java/org/rutebanken/netex/validation/NeTExValidatorTest.java index 1d9b00c7..a7498ba4 100644 --- a/src/test/java/org/rutebanken/netex/validation/NeTExValidatorTest.java +++ b/src/test/java/org/rutebanken/netex/validation/NeTExValidatorTest.java @@ -15,6 +15,7 @@ package org.rutebanken.netex.validation; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; @@ -24,20 +25,23 @@ import java.io.StringReader; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; class NeTExValidatorTest { - private final NeTExValidator neTExValidator = NeTExValidator.getNeTExValidator(); - - public NeTExValidatorTest() throws IOException, SAXException { } - - public static final String xml = "\n" + + private static final String MINIMAL_PUBLICATION_DELIVERY = "\n" + "\n" + " 2016-11-29T13:32:06.869+01:00\n" + " NSR\n" + ""; - + private NeTExValidator neTExValidator; + + @BeforeEach + void setUp() throws IOException, SAXException { + neTExValidator = NeTExValidator.getNeTExValidator(); + } + @Test void validationFailsForInvalidXml() { String xml = "\n" + @@ -48,8 +52,51 @@ void validationFailsForInvalidXml() { @Test void validatePublicationDeliveryWithLatestVersion() throws IOException, SAXException { - neTExValidator.validate(new StreamSource(new StringReader(xml))); + neTExValidator.validate(new StreamSource(new StringReader(MINIMAL_PUBLICATION_DELIVERY))); + } + + /** + * AssociatedContract / ContractRef on a ResponsibilityRoleAssignment was added in NeTEx 1.16.1 + * (entur/NeTEx#63). 1.16 is validated against the 1.16.1 schema, so it is valid against 1.16 too. + * It is not validated against 2.0 here: 2.0 enforces a key reference from ContractRef to a Contract + * declared in the same document, which 1.16.1 has no place for in a ResourceFrame. + */ + private static final String RESPONSIBILITY_SET_WITH_ASSOCIATED_CONTRACT = "\n" + + "\n" + + " 2026-09-10T10:00:00\n" + + " TST\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " Contracted operation\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + @Test + void latestVersionIs2_0() { + assertEquals(NeTExValidator.NetexVersion.v2_0, NeTExValidator.LATEST); + } + + @Test + void version1_16IsNamed1_16() { + assertEquals("1.16", NeTExValidator.NetexVersion.v1_16.toString()); + } + + @Test + void validateResponsibilitySetWithAssociatedContractAgainstVersion1_16() throws IOException, SAXException { + NeTExValidator validator = NeTExValidator.getNeTExValidator(NeTExValidator.NetexVersion.v1_16); + validator.validate(new StreamSource(new StringReader(RESPONSIBILITY_SET_WITH_ASSOCIATED_CONTRACT))); } } \ No newline at end of file diff --git a/src/test/java/org/rutebanken/util/LocalDateXmlAdapterTest.java b/src/test/java/org/rutebanken/util/LocalDateXmlAdapterTest.java new file mode 100644 index 00000000..6d7a37db --- /dev/null +++ b/src/test/java/org/rutebanken/util/LocalDateXmlAdapterTest.java @@ -0,0 +1,70 @@ +/* + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + */ + +package org.rutebanken.util; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class LocalDateXmlAdapterTest { + + private final LocalDateXmlAdapter adapter = new LocalDateXmlAdapter(); + + @Test + void testUnmarshalBasicDate() { + LocalDate result = adapter.unmarshal("2026-08-31"); + assertEquals(LocalDate.of(2026, 8, 31), result); + } + + @Test + void testUnmarshalDateWithOffset() { + LocalDate result = adapter.unmarshal("2026-05-03+02:00"); + assertEquals(LocalDate.of(2026, 5, 3), result); + } + + @Test + void testUnmarshalDateWithUtcDesignator() { + LocalDate result = adapter.unmarshal("2026-05-03Z"); + assertEquals(LocalDate.of(2026, 5, 3), result); + } + + @Test + void testUnmarshalDateTimeIsRejected() { + assertThrows(DateTimeParseException.class, () -> adapter.unmarshal("2026-05-03T00:00:00")); + } + + @Test + void testMarshalBasicDate() { + String result = adapter.marshal(LocalDate.of(2026, 8, 31)); + assertEquals("2026-08-31", result); + } + + @Test + void testMarshalNull() { + assertNull(adapter.marshal(null)); + } + + @Test + void testRoundTrip() { + LocalDate original = LocalDate.of(2026, 5, 3); + assertEquals(original, adapter.unmarshal(adapter.marshal(original))); + } +}