diff --git a/xtraplatform-features-gml/src/main/java/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGml.java b/xtraplatform-features-gml/src/main/java/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGml.java index 5e5dad317..1140cafd8 100644 --- a/xtraplatform-features-gml/src/main/java/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGml.java +++ b/xtraplatform-features-gml/src/main/java/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGml.java @@ -1029,7 +1029,9 @@ private Optional lookupChild( String wireUri = wireNamespaceUri == null ? "" : wireNamespaceUri; for (FeatureSchema p : parent.getProperties()) { String key = propertyKey(p, useAlias); - if (!wireLocalName.equals(stripPrefix(key))) { + String base = stripPrefix(key); + boolean suffixed = inputProfile.getObjectTypeSuffixedProperties().contains(base); + if (!(wireLocalName.equals(base) || (suffixed && wireLocalName.startsWith(base + "_")))) { continue; } String expectedUri = expectedNamespaceUri(p, parent, key); diff --git a/xtraplatform-features-gml/src/main/java/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGmlInputProfile.java b/xtraplatform-features-gml/src/main/java/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGmlInputProfile.java index 71b32c666..c64edbf07 100644 --- a/xtraplatform-features-gml/src/main/java/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGmlInputProfile.java +++ b/xtraplatform-features-gml/src/main/java/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGmlInputProfile.java @@ -10,6 +10,7 @@ import de.ii.xtraplatform.crs.domain.EpsgCrs; import java.util.List; import java.util.Map; +import java.util.Set; import org.immutables.value.Value; /** @@ -162,6 +163,18 @@ default String getFeatureMemberElementName() { Map> getValueWrap(); + /** + * Reverse of {@code GmlConfiguration#objectTypeSuffixedProperties}: the name (or alias, when + * {@link #getUseAlias()}) of each FEATURE_REF property whose GML element on the wire is the base + * property name plus a {@code _} suffix naming the referenced feature type (e.g. base + * {@code gehoertZuBauwerk} → {@code gehoertZuBauwerk_AX_Turm}). For these properties the decoder + * accepts an element whose local name is the base name optionally followed by a {@code + * _} suffix and maps it to the base property. The suffix is ignored: the referenced + * object type is carried independently through the FEATURE_REF join, so it need not be captured + * from the wire. + */ + Set getObjectTypeSuffixedProperties(); + static FeatureTokenDecoderGmlInputProfile empty() { return ImmutableFeatureTokenDecoderGmlInputProfile.builder().build(); } diff --git a/xtraplatform-features-gml/src/test/groovy/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGmlSpec.groovy b/xtraplatform-features-gml/src/test/groovy/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGmlSpec.groovy index 6489d5179..5daca3a24 100644 --- a/xtraplatform-features-gml/src/test/groovy/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGmlSpec.groovy +++ b/xtraplatform-features-gml/src/test/groovy/de/ii/xtraplatform/features/gml/domain/FeatureTokenDecoderGmlSpec.groovy @@ -516,6 +516,69 @@ class FeatureTokenDecoderGmlSpec extends Specification { !tokens.contains("urn:adv:oid:DENW36AL00000AAA") } + static final FeatureTokenDecoderGmlInputProfile NAS_TEMPLATES_SUFFIXED = + ImmutableFeatureTokenDecoderGmlInputProfile.builder() + .useAlias(true) + .featureRefTemplate("urn:adv:oid:{{value}}") + .addObjectTypeSuffixedProperties("istGebucht") + .build() + + def 'objectTypeSuffixedProperties: a _-suffixed element maps to the base feature-ref property'() { + given: + // ALKIS NAS names this element adv:gehoertZuBauwerk_AX_Turm; istGebucht stands in here as a + // declared suffixed property. The _AX_Turm suffix is ignored and the href is reduced as for + // the plain element. + def decoder = newDecoder(axFlurstueckWithRefsSchema(), NAS_TEMPLATES_SUFFIXED) + def xml = """ + + """ + + when: + def tokens = runDecoder(decoder, xml) + + then: + valueAtPath(tokens, ["11001-21008"]) == "DENW36ALl800005x" + } + + def 'objectTypeSuffixedProperties: the unsuffixed element still matches a declared property'() { + given: + def decoder = newDecoder(axFlurstueckWithRefsSchema(), NAS_TEMPLATES_SUFFIXED) + def xml = """ + + """ + + when: + def tokens = runDecoder(decoder, xml) + + then: + valueAtPath(tokens, ["11001-21008"]) == "DENW36ALl800005x" + } + + def 'a suffixed element is not matched when the property is not declared in objectTypeSuffixedProperties'() { + given: + // Same element, but the default profile declares no suffixed properties: the suffix is not + // stripped, the element matches no property and is skipped (the FK is not emitted). + def decoder = newDecoder(axFlurstueckWithRefsSchema(), NAS_TEMPLATES) + def xml = """ + + """ + + when: + def tokens = runDecoder(decoder, xml) + + then: + valueAtPath(tokens, ["11001-21008"]) == null + } + def 'xlink:href is emitted unchanged when no template is configured'() { given: def profile = ImmutableFeatureTokenDecoderGmlInputProfile.builder()