highsource / jsonix

Powerful XML<->JSON JavaScript mapping library.
BSD 2-Clause "Simplified" License
361 stars 78 forks source link

WFS Filter PropertyName lost #161

Open urothstein opened 7 years ago

urothstein commented 7 years ago

Using the following WFS-Filter we want to reduce the response from the WFS to the attributes 'ogc_fid' and 'cat'. It works properly with geoserver.

<wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:ogc="http://www.opengis.net/ogc" xmlns:gml="http://www.opengis.net/gml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xlink="http://www.w3.org/1999/xlink" service="WFS" version="2.0.0" startIndex="0" count="25" outputFormat="application/json">
<wfs:Query typeNames="EOME:WRS2">
<wfs:PropertyName>ogc_fid</wfs:PropertyName>
<wfs:PropertyName>cat</wfs:PropertyName>
  <fes:Filter xmlns:fes="http://www.opengis.net/fes/2.0">
 <fes:And>
   <fes:PropertyIsBetween><fes:ValueReference>sensing_time</fes:ValueReference>
    <fes:LowerBoundary>
      <fes:Literal>2013-02-02T13:00:00.000Z</fes:Literal>
    </fes:LowerBoundary>
    <fes:UpperBoundary>
      <fes:Literal>2017-01-03T15:20:47.654Z</fes:Literal>
    </fes:UpperBoundary>
  </fes:PropertyIsBetween>
 </fes:And>
</fes:Filter>
</wfs:Query></wfs:GetFeature>

Converting this XML to JSON with JSONIX produces the following result:

{
    "name": {
        "namespaceURI": "http://www.opengis.net/wfs/2.0",
        "localPart": "GetFeature",
        "prefix": "wfs",
        "key": "{http://www.opengis.net/wfs/2.0}GetFeature",
        "string": "{http://www.opengis.net/wfs/2.0}wfs:GetFeature"
    },
    "value": {
        "TYPE_NAME": "WFS_2_0.GetFeatureType",
        "service": "WFS",
        "version": "2.0.0",
        "startIndex": 0,
        "count": 25,
        "outputFormat": "application/json",
        "abstractQueryExpression": [{
            "name": {
                "namespaceURI": "http://www.opengis.net/wfs/2.0",
                "localPart": "Query",
                "prefix": "wfs",
                "key": "{http://www.opengis.net/wfs/2.0}Query",
                "string": "{http://www.opengis.net/wfs/2.0}wfs:Query"
            },
            "value": {
                "TYPE_NAME": "WFS_2_0.QueryType",
                "typeNames": ["EOME:WRS2"],
                "abstractSortingClause": {
                    "name": {
                        "namespaceURI": "http://www.opengis.net/wfs/2.0",
                        "localPart": "PropertyName",
                        "prefix": "wfs",
                        "key": "{http://www.opengis.net/wfs/2.0}PropertyName",
                        "string": "{http://www.opengis.net/wfs/2.0}wfs:PropertyName"
                    },
                    "value": {
                        "TYPE_NAME": "WFS_2_0.PropertyName",
                        "value": {
                            "namespaceURI": "",
                            "localPart": "cat",
                            "prefix": "",
                            "key": "cat",
                            "string": "cat"
                        }
                    }
                }
            }
        }]
    }
}

It seems that only the last PropertyName ('cat') has been considered. The PropertyName 'ogc_fid' has been ignored. Vice versa we have the same problem to generate a json and convert it to XML with several PropertyNames so that the result-XML looks like the XML-example (see above).

Thank you and best regards

ricky92 commented 5 years ago

Sorry to bring this up after quite some time, but I think it would be redundant to file a new issue for the same problem.

I have encountered this same issue with both the unmarshalling and marshalling of a WFS GetFeature element. This seems to be related to a bug in Jsonix's handling of substitutionGroups and abstract elements/types.

More specifically, the wfs:QueryType extends fes:AbstractAdhocQueryExpressionType, which in turn extends fes:AbstractQueryExpressionType. This last type holds three references to abstract elements, fes:AbstractProjectionClause, fes:AbstractSelectionClause and fes:AbstractSortingClause. These elements are AnyType and there seems to be some constraint in Jsonix's type/structure internal object where there can only be one AnyType element.

The code where I think I found the bug is here, in the unmarshal function:

// New sub-element starts
var elementNameKey = input.getNameKey();
if (Jsonix.Util.Type
        .exists(this.structure.elements[elementNameKey])) {
    var elementPropertyInfo = this.structure.elements[elementNameKey];
    this.unmarshalProperty(context, input,
            elementPropertyInfo, result);
} else if (Jsonix.Util.Type
        .exists(this.structure.any)) {
    // TODO Refactor

    var anyPropertyInfo = this.structure.any;
    this.unmarshalProperty(context, input,
            anyPropertyInfo, result);
} else {
    // TODO optionally report a validation error that the element is not expected
    et = input.skipElement();
}

So, the first if condition is not met as it does not take into account the substitutions. The second, instead, is true as the structure does have an AnyType element, but it wrongly references just the last one. In this case, AbstractSortingClause. And this seems to be the reason why the unmarshaller puts the PropertyName under the wrong element (while it's supposed to go in the AbstractProjectionClause).

I also suspect that for the reasons I just listed, the handling of an AnyType collection might be broken. One such example is exactly the AbstractProjectionClause element (minOccurs=0, maxOccurs=unbounded).

And all this is just for the unmarshalling. I'm having a hard time trying to do the marshalling as well, and I am quite positive similar issues exist in the marshalling process.

@highsource Could you confirm if those issues do indeed exist? I could attempt a fix on my own but I am not familiar with this library's structure and code. Thank you.

Riccardo

highsource commented 5 years ago

@ricky92

There is indeed a problem, but not in Jsonix.

It is complicated so please bear with me.

OGC schemas sometimes use substitution groups as extension points. For instance, Filter 2.0 defines the following elements:

   <xsd:element name="AbstractProjectionClause" abstract="true"/>
   <xsd:complexType name="AbstractProjectionClauseType" abstract="true"/>

   <xsd:element name="AbstractSelectionClause" abstract="true"/>
   <xsd:complexType name="AbstractSelectionClauseType" abstract="true"/>

   <xsd:element name="AbstractSortingClause" abstract="true"/>
   <xsd:complexType name="AbstractSortingClauseType" abstract="true"/>

Other schemas define elements which the may substitute these abstract elements. Like WFS 2.0:

   <xsd:element name="PropertyName"
      substitutionGroup="fes:AbstractProjectionClause">
      ...
   </xsd:element>

For this to work, Jsonix has to generate element reference properties.

The problem is in how JAXB (the tech Jsonix is based on) processes abstract elements.

If JAXB sees other elements which may replace this element, it will generate element reference properties.
If there are no elements to replace this element, it will be generated as an element property.

When compiling schemas separately (i.e. Filter 2.0 without WFS 2.0), some of the abstract elements designated to be substitution group heads will not have substitutes in the given schema set. So JAXB desides that these should be simple element properties. This leads to the problem that these elements cannot be replaced by anything else.

So far I did not found a better solution rather than simply adding at least one substitution element per designated substitution group head. For instance, in Filter 2.0:

   <xsd:element name="ProjectionClauseExtension" substitutionGroup="fes:AbstractProjectionClause"/>
   <xsd:element name="SelectionClauseExtension" substitutionGroup="fes:AbstractSelectionClause"/>
   <xsd:element name="SortingClauseExtension" substitutionGroup="fes:AbstractSortingClause"/>

With this you get element reference properties like it should be.

This requires patching schemas which I actually do in ogc-schemas.

Ok, to sum up - yes, there is a problem but I do not think it is a bug in Jsonix.

So, the first if condition is not met as it does not take into account the substitutions.

It would have taken substitution into account if they were defined in mappings. But due to the reasons listed above, they are not.

ricky92 commented 5 years ago

@highsource Thank you so much for your quick response.

I understand that there is a problem in how JAXB handles abstract elements, but, as you said, you provided a patch/workaround to 'fix' those mappings.

After digging some more in the code and in the schemas, though, I noticed that the workaround you applied to mark those abstract elements as elementRefs works for all but the AbstractProjectionClause element.

In fact, as you can see in the Filter_2_0 schema, such element does not even have a type specified:

        ln: 'AbstractAdhocQueryExpressionType',
        bti: '.AbstractQueryExpressionType',
        ps: [{
            n: 'abstractProjectionClause',
            mno: 0,
            col: true,
            en: 'AbstractProjectionClause',
            ti: 'AnyType',
            // There should be the following line here:
            // t: 'er'
          }, {
            n: 'abstractSelectionClause',
            mx: false,
            dom: false,
            en: 'AbstractSelectionClause',
            ti: 'AnyType',
            t: 'er'
          }, {
            n: 'abstractSortingClause',
            mx: false,
            dom: false,
            en: 'AbstractSortingClause',
            ti: 'AnyType',
            t: 'er'
          },

After modifying the schema manually, it now works as expected. The example request gets unmarshalled correctly and the PropertyNames end up in the abstractProjectionClause object property.

Marshalling works as well, for example an object with this structure:

{
  "wfs:GetFeature":{
    "TYPE_NAME":"WFS_2_0.GetFeatureType",
    "service":"WFS",
    "version":"2.0.0",
    "outputFormat":"application/json",
    "count":20,
    "abstractQueryExpression":[
      {
        "wfs:Query":{
          "TYPE_NAME":"WFS_2_0.QueryType",
          "abstractProjectionClause":[
            {
              "wfs:PropertyName":{
                "TYPE_NAME":"WFS_2_0.PropertyName",
                "value":{
                  "namespaceURI":"http://example.org/",
                  "localPart":"zone",
                  "prefix":"example",
                  "key":"{http://example.org/}zone",
                  "string":"{http://example.org/}example:zone"
                }
              }
            },
            {
              "wfs:PropertyName":{
                "TYPE_NAME":"WFS_2_0.PropertyName",
                "value":{
                  "namespaceURI":"http://example.org/",
                  "localPart":"name",
                  "prefix":"example",
                  "key":"{http://example.org/}name",
                  "string":"{http://example.org/}example:name"
                }
              }
            }
          ],
          "typeNames":[
            "example:test"
          ]
        }
      }
    ]
  }
}

...gets correctly marshalled to:

<wfs:GetFeature xmlns:wfs="http://www.opengis.net/wfs" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dct="http://purl.org/dc/terms/" xmlns:fes="http://www.opengis.net/fes" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:p0="http://www.opengis.net/wfs/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="p0:GetFeatureType" service="WFS" version="2.0.0" count="20" outputFormat="application/json">
  <wfs:Query xsi:type="p0:QueryType" typeNames="example:test">
    <wfs:PropertyName xmlns:example="http://example.org/" xsi:type="p0:PropertyName">example:zone</wfs:PropertyName>
    <wfs:PropertyName xmlns:example="http://example.org/" xsi:type="p0:PropertyName">example:name</wfs:PropertyName>
  </wfs:Query>
</wfs:GetFeature>

So I guess this means that there is some problem in how the schema gets compiled. I cannot really submit a fix to the ogc-schemas repository as I only found where the problem lies, but not its cause. I hope this information can help you fix the issue, though. Thank you again.