Skip to content

Commit

Permalink
[GEOS-9180] Remove property/element alternation in complex JSON output
Browse files Browse the repository at this point in the history
  • Loading branch information
aaime committed Apr 11, 2019
1 parent 7a4eca5 commit 8e5446c
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 29 deletions.
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ public void testGetFeatureJSON() throws Exception {
assertFalse(color.has("geometry")); assertFalse(color.has("geometry"));
assertFalse(color.has("properties")); assertFalse(color.has("properties"));
// but value and codespace right in instead // but value and codespace right in instead
color = color.getJSONObject("CGI_TermValue").getJSONObject("value"); color = color.getJSONObject("value");
assertThat(color.getString("value"), anyOf(is("Blue"), is("Yellow"))); assertThat(color.getString("value"), anyOf(is("Blue"), is("Yellow")));
assertThat(color.getString("@codeSpace"), is("some:uri")); assertThat(color.getString("@codeSpace"), is("some:uri"));
} }
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -134,9 +134,7 @@ public void testSimpleContentTimeEncoding() throws Exception {
getNestedObject( getNestedObject(
properties, properties,
"relatedSamplingFeature", "relatedSamplingFeature",
"SamplingFeatureComplex",
"relatedSamplingFeature", "relatedSamplingFeature",
"SF_Specimen",
"properties", "properties",
"samplingTime", "samplingTime",
"TimeInstant"); "TimeInstant");
Expand All @@ -156,12 +154,7 @@ public void testOneDimensionalEncoding() throws Exception {
assertThat(properties, is(notNullValue())); assertThat(properties, is(notNullValue()));
JSONObject samplingLocation = JSONObject samplingLocation =
getNestedObject( getNestedObject(
properties, properties, "relatedSamplingFeature", "relatedSamplingFeature", "geometry");
"relatedSamplingFeature",
"SamplingFeatureComplex",
"relatedSamplingFeature",
"SF_Specimen",
"geometry");
JSONArray coordinates = samplingLocation.getJSONArray("coordinates"); JSONArray coordinates = samplingLocation.getJSONArray("coordinates");
assertThat(coordinates.size(), is(2)); assertThat(coordinates.size(), is(2));
JSONArray c1 = coordinates.getJSONArray(0); JSONArray c1 = coordinates.getJSONArray(0);
Expand All @@ -179,7 +172,7 @@ public void testNestedFeatureEncoding() throws Exception {
print(json); print(json);
JSONObject properties = getFeaturePropertiesById(json, "BOREHOLE.WTB5"); JSONObject properties = getFeaturePropertiesById(json, "BOREHOLE.WTB5");
assertThat(properties, is(notNullValue())); assertThat(properties, is(notNullValue()));
JSONObject collar = getNestedObject(properties, "collarLocation", "BoreholeCollar"); JSONObject collar = getNestedObject(properties, "collarLocation");
assertEquals("BOREHOLE.COLLAR.WTB5", collar.getString("id")); assertEquals("BOREHOLE.COLLAR.WTB5", collar.getString("id"));
assertEquals("Feature", collar.getString("type")); assertEquals("Feature", collar.getString("type"));
JSONObject collarGeometry = collar.getJSONObject("geometry"); JSONObject collarGeometry = collar.getJSONObject("geometry");
Expand Down
106 changes: 87 additions & 19 deletions src/wfs/src/main/java/org/geoserver/wfs/json/ComplexGeoJsonWriter.java
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@
import org.opengis.feature.type.PropertyType; import org.opengis.feature.type.PropertyType;
import org.opengis.filter.identity.Identifier; import org.opengis.filter.identity.Identifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.xml.sax.Attributes;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;


/** GeoJSON writer capable of handling complex features. */ /** GeoJSON writer capable of handling complex features. */
class ComplexGeoJsonWriter { class ComplexGeoJsonWriter {
Expand Down Expand Up @@ -318,8 +328,7 @@ private List<Map<NameImpl, String>> getLinkedFeatures(List<Property> properties)
for (Property property : properties) { for (Property property : properties) {
// get the attributes (XML attributes) associated with the current property // get the attributes (XML attributes) associated with the current property
Map<NameImpl, String> attributes = Map<NameImpl, String> attributes =
(Map<NameImpl, String>) (Map<NameImpl, String>) property.getUserData().get(Attributes.class);
property.getUserData().get(org.xml.sax.Attributes.class);
if (checkIfFeatureIsLinked(property, attributes)) { if (checkIfFeatureIsLinked(property, attributes)) {
// we have a linked features // we have a linked features
linkedFeatures.add(attributes); linkedFeatures.add(attributes);
Expand Down Expand Up @@ -371,26 +380,43 @@ private <T> T getElementAt(Collection<T> collection, int index) {
private void encodeProperty(Property property) { private void encodeProperty(Property property) {
// these extra attributes should be seen as XML attributes // these extra attributes should be seen as XML attributes
Map<NameImpl, String> attributes = Map<NameImpl, String> attributes =
(Map<NameImpl, String>) property.getUserData().get(org.xml.sax.Attributes.class); (Map<NameImpl, String>) property.getUserData().get(Attributes.class);
String attributeName = property.getName().getLocalPart();
encodeProperty(attributeName, property, attributes);
}

private void encodeProperty(
String attributeName, Property property, Map<NameImpl, String> attributes) {
if (property instanceof ComplexAttribute) { if (property instanceof ComplexAttribute) {
// check if we have a simple content // check if we have a simple content
ComplexAttribute complexAttribute = (ComplexAttribute) property; ComplexAttribute complexAttribute = (ComplexAttribute) property;

Object simpleValue = getSimpleContent(complexAttribute); Object simpleValue = getSimpleContent(complexAttribute);
if (simpleValue != null) { if (simpleValue != null) {
encodeSimpleAttribute( encodeSimpleAttribute(attributeName, simpleValue, attributes);
complexAttribute.getName().getLocalPart(), simpleValue, attributes);
} else { } else {
// we need to encode a complex attribute // skip the property/element nesting found in GML, if possible
encodeComplexAttribute((ComplexAttribute) property, attributes); if (isGMLPropertyType(complexAttribute)) {
Collection<? extends Property> value = complexAttribute.getValue();
Property nested = value.iterator().next();
Map<NameImpl, String> nestedAttributes =
(Map<NameImpl, String>) nested.getUserData().get(Attributes.class);
Map<NameImpl, String> mergedAttributes =
mergeMaps(attributes, nestedAttributes);
encodeProperty(attributeName, nested, mergedAttributes);
} else {
// we need to encode a normal complex attribute
encodeComplexAttribute(attributeName, complexAttribute, attributes);
}
} }
} else if (property instanceof Attribute) { } else if (property instanceof Attribute) {
// check if we have a feature or list of features (chained features) // check if we have a feature or list of features (chained features)
List<Feature> features = getFeatures((Attribute) property); List<Feature> features = getFeatures((Attribute) property);
if (features != null) { if (features != null) {
encodeChainedFeatures(property.getName().getLocalPart(), features); encodeChainedFeatures(attributeName, features);
} else { } else {
// we need to encode a simple attribute // we need to encode a simple attribute
encodeSimpleAttribute((Attribute) property, attributes); encodeSimpleAttribute(attributeName, property.getValue(), attributes);
} }
} else { } else {
// unsupported attribute type provided, this will unlikely happen // unsupported attribute type provided, this will unlikely happen
Expand All @@ -401,6 +427,58 @@ private void encodeProperty(Property property) {
} }
} }


private <K, V> Map<K, V> mergeMaps(Map<K, V> mapA, Map<K, V> mapB) {
if (mapA == null) {
return mapB;
} else if (mapB == null) {
return mapA;
}

Map<K, V> merged = new HashMap<>(mapA);
merged.putAll(mapB);
return merged;
}

/**
* This code tries to determine if the current complex attribute is an example of GML
* property/type alternation. The GML gives us pretty much no firm indication to recognize them,
* there is no substitution group or inheritance, there are attribute groups sometimes found in
* these constructs, but not mandatory and not always present at the schema level.
*
* <p>This code works by recognizing the common alternation nomenclature, that is:
*
* <ul>
* <li>The attribute type is called ${name}PropertyType
* <li>It contains a single element inside, which is in turn another complex attribute itself
* <li>The contained element type is called ${name}Type or the property is called ${name}
* </ul>
*
* Can I just say.... HACK HACK HACK!
*/
private boolean isGMLPropertyType(ComplexAttribute complexAttribute) {
String attributeName = complexAttribute.getType().getName().getLocalPart();
if (!attributeName.endsWith("PropertyType")) {
return false;
}
Collection<? extends Property> value = complexAttribute.getValue();
if (value.size() != 1) {
return false;
}
Property containedProperty = value.iterator().next();
String containedPropertyTypeName = containedProperty.getType().getName().getLocalPart();
String containedPropertyName = containedProperty.getName().getLocalPart();
String propertyTypePrefix;
if (attributeName.endsWith("_PropertyType")) {
propertyTypePrefix =
attributeName.substring(0, attributeName.length() - "_PropertyType".length());
} else {
propertyTypePrefix =
attributeName.substring(0, attributeName.length() - "PropertyType".length());
}
return containedPropertyTypeName.equals(propertyTypePrefix + "Type")
|| containedPropertyName.equals(propertyTypePrefix);
}

/** /**
* Helper method that try to extract a list of features from an attribute. If no features can be * Helper method that try to extract a list of features from an attribute. If no features can be
* found NULL is returned. * found NULL is returned.
Expand Down Expand Up @@ -505,16 +583,6 @@ private boolean isFullFeature(ComplexAttribute attribute) {
|| !NON_FEATURE_TYPE_PROXY.isInstance(attribute.getType())); || !NON_FEATURE_TYPE_PROXY.isInstance(attribute.getType()));
} }


/**
* Encode a simple attribute, this means that this property will be encoded as a simple JSON
* attribute.
*/
private void encodeSimpleAttribute(Attribute attribute, Map<NameImpl, String> attributes) {
String name = attribute.getName().getLocalPart();
Object value = attribute.getValue();
encodeSimpleAttribute(name, value, attributes);
}

/** /**
* Encode a simple attribute, this means that this property will be encoded as a simple JSON * Encode a simple attribute, this means that this property will be encoded as a simple JSON
* attribute if no attributes are available, otherwise it will be encoded as an array containing * attribute if no attributes are available, otherwise it will be encoded as an array containing
Expand Down

0 comments on commit 8e5446c

Please sign in to comment.