diff --git a/README.md b/README.md
index 4520b5ab..6cec8b98 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ Java model classes generated from [NeTEx](https://github.com/entur/NeTEx) (Netwo
This library downloads the NeTEx XSD files from the [Entur NeTEx fork](https://github.com/entur/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.
-- **NeTEx version**: 1.16 (model generation), with legacy validation support back to 1.07
+- **NeTEx version**: 1.16.1 (model generation), with legacy validation support back to 1.07
- **JAXB**: Jakarta XML Bind 4.x (Jakarta EE 9+)
- **Generated package**: `org.rutebanken.netex.model`
- **GML types package**: `net.opengis.gml._3`
@@ -103,7 +103,7 @@ The XSD files are downloaded from GitHub during the `generate-sources` phase and
- Property name conflict resolution
- Package mapping to `org.rutebanken.netex.model`
-**Schema validation**: `NeTExValidator` supports validation against NeTEx versions 1.07 through 1.16.
+**Schema validation**: `NeTExValidator` supports validation against NeTEx versions 1.07 through 1.16. 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.
**Note on GML types**: This library generates classes under `net.opengis.gml._3` as part of the NeTEx model.
diff --git a/bindings.xjb b/bindings.xjb
index 089dc6c0..ffcaccde 100644
--- a/bindings.xjb
+++ b/bindings.xjb
@@ -14,14 +14,14 @@
-|
+|
+ schemaLocation="./src/main/resources/xsd/1.16.1/netex_framework/netex_genericFramework/netex_organisation_version.xsd">
@@ -30,35 +30,35 @@
+ schemaLocation="./src/main/resources/xsd/1.16.1/netex_part_3/part3_fares/netex_salesOfferPackage_version.xsd">
+ schemaLocation="./src/main/resources/xsd/1.16.1/netex_part_2/part2_journeyTimes/netex_vehicleJourneyFrequency_version.xsd">
+ schemaLocation="./src/main/resources/xsd/1.16.1/netex_part_3/part3_salesTransactions/netex_salesContract_version.xsd">
+ schemaLocation="./src/main/resources/xsd/1.16.1/netex_part_3/part3_salesTransactions/netex_retailConsortium_version.xsd">
+ schemaLocation="./src/main/resources/xsd/1.16.1/netex_framework/netex_responsibility/netex_version_support.xsd">
@@ -71,7 +71,7 @@
+ schemaLocation="./src/main/resources/xsd/1.16.1/netex_framework/netex_genericFramework/netex_zone_version.xsd">
diff --git a/pom.xml b/pom.xml
index c3d8dbba..f6aa7eb9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,9 +69,9 @@
11
NeTEx
- master
+ tags/v1.0.16.1
https://github.com/entur/${netexRepoName}
- 1.16
+ 1.16.1
4.0.5
@@ -285,7 +285,7 @@
${netexGithubUrl}/archive/${netexBranch}.zip
src/main/resources/xsd/${netexVersion}
- NeTEx-${netexBranch}/xsd/*
+ NeTEx-tags-v1.0.16.1/xsd/*
./bin/netex-download-extract.sh
diff --git a/src/main/java/org/rutebanken/netex/validation/NeTExValidator.java b/src/main/java/org/rutebanken/netex/validation/NeTExValidator.java
index 4b3f4a83..7cd69393 100644
--- a/src/main/java/org/rutebanken/netex/validation/NeTExValidator.java
+++ b/src/main/java/org/rutebanken/netex/validation/NeTExValidator.java
@@ -44,17 +44,36 @@ 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 same schema the model is generated from.
+ */
+ v1_16 ("1.16", "1.16.1"),
+ /**
+ * @deprecated the 1.16.1 schema is what {@link #v1_16} validates against. Use {@link #v1_16}.
+ */
+ @Deprecated
+ v1_16_1 ("1.16.1");
+
+ 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;
@@ -102,7 +121,7 @@ public NeTExValidator() throws IOException, SAXException {
public 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/test/java/org/rutebanken/netex/model/UnmarshalResourceFrameExtendedTest.java b/src/test/java/org/rutebanken/netex/model/UnmarshalResourceFrameExtendedTest.java
index 2ee3ea29..7ca3c6bb 100644
--- a/src/test/java/org/rutebanken/netex/model/UnmarshalResourceFrameExtendedTest.java
+++ b/src/test/java/org/rutebanken/netex/model/UnmarshalResourceFrameExtendedTest.java
@@ -121,4 +121,51 @@ void unmarshalOperatorWithBrandingRef() throws JAXBException {
assertNotNull(operator.getBrandingRef());
assertEquals("TST:Branding:1", operator.getBrandingRef().getRef());
}
+
+ @Test
+ void unmarshalResponsibilitySetWithAssociatedContract() throws JAXBException {
+
+ String xml = "\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" +
+ "";
+
+ Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
+
+ @SuppressWarnings("unchecked")
+ JAXBElement jaxbElement = (JAXBElement) unmarshaller
+ .unmarshal(new ByteArrayInputStream(xml.getBytes()));
+
+ PublicationDeliveryStructure publicationDeliveryStructure = jaxbElement.getValue();
+ ResourceFrame resourceFrame = (ResourceFrame) publicationDeliveryStructure.getDataObjects().getCompositeFrameOrCommonFrame().get(0).getValue();
+
+ ResponsibilitySet responsibilitySet = resourceFrame.getResponsibilitySets().getResponsibilitySet().get(0);
+ assertEquals("Contracted operation", responsibilitySet.getName().getValue());
+
+ ResponsibilityRoleAssignment_VersionedChildStructure roleAssignment = responsibilitySet.getRoles().getResponsibilityRoleAssignment().get(0);
+
+ ContractRef_RelStructure associatedContract = roleAssignment.getAssociatedContract();
+ assertNotNull(associatedContract);
+ ContractRefStructure contractRef = associatedContract.getContractRef();
+ assertNotNull(contractRef);
+ assertEquals("TST:Contract:1", contractRef.getRef());
+ assertEquals("1", contractRef.getVersion());
+ }
}
diff --git a/src/test/java/org/rutebanken/netex/validation/NeTExValidatorTest.java b/src/test/java/org/rutebanken/netex/validation/NeTExValidatorTest.java
index 1d9b00c7..74168092 100644
--- a/src/test/java/org/rutebanken/netex/validation/NeTExValidatorTest.java
+++ b/src/test/java/org/rutebanken/netex/validation/NeTExValidatorTest.java
@@ -24,6 +24,7 @@
import java.io.StringReader;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertEquals;
class NeTExValidatorTest {
@@ -52,4 +53,58 @@ void validatePublicationDeliveryWithLatestVersion() throws IOException, SAXExcep
}
-}
\ No newline at end of file
+ /**
+ * 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.
+ */
+ 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 latestVersionIs1_16() {
+ assertEquals(NeTExValidator.NetexVersion.v1_16, NeTExValidator.LATEST);
+ }
+
+ @Test
+ void version1_16IsNamed1_16() {
+ assertEquals("1.16", NeTExValidator.NetexVersion.v1_16.toString());
+ }
+
+ @Test
+ void validateResponsibilitySetWithAssociatedContractAgainstLatestVersion() throws IOException, SAXException {
+ neTExValidator.validate(new StreamSource(new StringReader(RESPONSIBILITY_SET_WITH_ASSOCIATED_CONTRACT)));
+ }
+
+ @Test
+ void validateResponsibilitySetWithAssociatedContractAgainstVersion1_16() throws IOException, SAXException {
+ NeTExValidator validator1_16 = NeTExValidator.getNeTExValidator(NeTExValidator.NetexVersion.v1_16);
+ validator1_16.validate(new StreamSource(new StringReader(RESPONSIBILITY_SET_WITH_ASSOCIATED_CONTRACT)));
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ void validateResponsibilitySetWithAssociatedContractAgainstDeprecatedVersion1_16_1() throws IOException, SAXException {
+ NeTExValidator validator1_16_1 = NeTExValidator.getNeTExValidator(NeTExValidator.NetexVersion.v1_16_1);
+ validator1_16_1.validate(new StreamSource(new StringReader(RESPONSIBILITY_SET_WITH_ASSOCIATED_CONTRACT)));
+ }
+
+}