diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/helper/ExpandTreeBuilder.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/helper/ExpandTreeBuilder.java index 882213c21..afa8d70eb 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/helper/ExpandTreeBuilder.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/helper/ExpandTreeBuilder.java @@ -19,13 +19,17 @@ package org.apache.olingo.server.core.deserializer.helper; import org.apache.olingo.commons.api.edm.EdmNavigationProperty; +import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.server.core.uri.UriInfoImpl; import org.apache.olingo.server.core.uri.UriResourceNavigationPropertyImpl; import org.apache.olingo.server.core.uri.queryoption.ExpandItemImpl; public abstract class ExpandTreeBuilder { + public abstract ExpandTreeBuilder expand(EdmNavigationProperty edmNavigationProperty); + public abstract ExpandOption build(); + protected ExpandItemImpl buildExpandItem(final EdmNavigationProperty edmNavigationProperty) { return new ExpandItemImpl() .setResourcePath(new UriInfoImpl() diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/helper/ExpandTreeBuilderImpl.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/helper/ExpandTreeBuilderImpl.java index b0287ff18..4232f9880 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/helper/ExpandTreeBuilderImpl.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/helper/ExpandTreeBuilderImpl.java @@ -18,6 +18,9 @@ */ package org.apache.olingo.server.core.deserializer.helper; +import java.util.HashMap; +import java.util.Map; + import org.apache.olingo.commons.api.edm.EdmNavigationProperty; import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.server.core.uri.queryoption.ExpandItemImpl; @@ -25,43 +28,41 @@ import org.apache.olingo.server.core.uri.queryoption.ExpandOptionImpl; public class ExpandTreeBuilderImpl extends ExpandTreeBuilder { + private final Map childBuilderCache = new HashMap(); + private final ExpandItemImpl parentItem; private ExpandOptionImpl expandOption = null; + private ExpandTreeBuilderImpl(final ExpandItemImpl parentItem) { + this.parentItem = parentItem; + } + + @Override public ExpandTreeBuilder expand(final EdmNavigationProperty edmNavigationProperty) { - ExpandItemImpl expandItem = buildExpandItem(edmNavigationProperty); - if (expandOption == null) { expandOption = new ExpandOptionImpl(); + if(parentItem != null && parentItem.getExpandOption() == null){ + parentItem.setSystemQueryOption(expandOption); + } } - expandOption.addExpandItem(expandItem); - - return new ExpandTreeBuilderInner(expandItem); + + ExpandTreeBuilder builder = childBuilderCache.get(edmNavigationProperty.getName()); + if(builder == null){ + ExpandItemImpl expandItem = buildExpandItem(edmNavigationProperty); + expandOption.addExpandItem(expandItem); + builder = new ExpandTreeBuilderImpl(expandItem); + childBuilderCache.put(edmNavigationProperty.getName(), builder); + } + + return builder; } + @Override public ExpandOption build() { return expandOption; } - - private class ExpandTreeBuilderInner extends ExpandTreeBuilder { - private ExpandItemImpl parent; - - public ExpandTreeBuilderInner(final ExpandItemImpl expandItem) { - parent = expandItem; - } - - @Override - public ExpandTreeBuilder expand(final EdmNavigationProperty edmNavigationProperty) { - if (parent.getExpandOption() == null) { - final ExpandOptionImpl expandOption = new ExpandOptionImpl(); - parent.setSystemQueryOption(expandOption); - } - - final ExpandItemImpl expandItem = buildExpandItem(edmNavigationProperty); - ((ExpandOptionImpl) parent.getExpandOption()).addExpandItem(expandItem); - - return new ExpandTreeBuilderInner(expandItem); - } - + + public static ExpandTreeBuilder create(){ + return new ExpandTreeBuilderImpl(null); } } \ No newline at end of file diff --git a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java index de1fa395f..3398dc70d 100644 --- a/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java +++ b/lib/server-core/src/main/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializer.java @@ -134,7 +134,7 @@ public class ODataJsonDeserializer implements ODataDeserializer { throw new DeserializerException("Nested Arrays and primitive values are not allowed for an entity value.", DeserializerException.MessageKeys.INVALID_ENTITY); } - EdmEntityType derivedEdmEntityType = (EdmEntityType)getDerivedType(edmEntityType, arrayElement); + EdmEntityType derivedEdmEntityType = (EdmEntityType) getDerivedType(edmEntityType, arrayElement); entities.add(consumeEntityNode(derivedEdmEntityType, (ObjectNode) arrayElement, expandBuilder)); } return entities; @@ -149,9 +149,9 @@ public class ODataJsonDeserializer implements ODataDeserializer { throws DeserializerException { try { final ObjectNode tree = parseJsonTree(stream); - final ExpandTreeBuilderImpl expandBuilder = new ExpandTreeBuilderImpl(); + final ExpandTreeBuilder expandBuilder = ExpandTreeBuilderImpl.create(); - EdmEntityType derivedEdmEntityType = (EdmEntityType)getDerivedType(edmEntityType, tree); + EdmEntityType derivedEdmEntityType = (EdmEntityType) getDerivedType(edmEntityType, tree); return DeserializerResultImpl.with().entity(consumeEntityNode(derivedEdmEntityType, tree, expandBuilder)) .expandOption(expandBuilder.build()) @@ -279,8 +279,8 @@ public class ODataJsonDeserializer implements ODataDeserializer { public Parameter parameter(final String content, final EdmParameter parameter) throws DeserializerException { try { JsonParser parser = new JsonFactory(new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true)) - .createParser(content); + .configure(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY, true)) + .createParser(content); JsonNode node = parser.getCodec().readTree(parser); if (node == null) { throw new DeserializerException("Invalid JSON syntax.", @@ -384,30 +384,29 @@ public class ODataJsonDeserializer implements ODataDeserializer { final EdmNavigationProperty edmNavigationProperty) throws DeserializerException { Link link = new Link(); link.setTitle(navigationPropertyName); - final ExpandTreeBuilder childExpandBuilder = (expandBuilder != null) ? - expandBuilder.expand(edmNavigationProperty) : null; - EdmEntityType derivedEdmEntityType = (EdmEntityType)getDerivedType( - edmNavigationProperty.getType(), jsonNode); - if (jsonNode.isArray() && edmNavigationProperty.isCollection()) { - link.setType(Constants.ENTITY_SET_NAVIGATION_LINK_TYPE); - EntityCollection inlineEntitySet = new EntityCollection(); - inlineEntitySet.getEntities().addAll( - consumeEntitySetArray(derivedEdmEntityType, jsonNode, childExpandBuilder)); - link.setInlineEntitySet(inlineEntitySet); - } else if (!jsonNode.isArray() && (!jsonNode.isValueNode() || jsonNode.isNull()) - && !edmNavigationProperty.isCollection()) { - link.setType(Constants.ENTITY_NAVIGATION_LINK_TYPE); - if (!jsonNode.isNull()) { - Entity inlineEntity = consumeEntityNode(derivedEdmEntityType, (ObjectNode) jsonNode, - childExpandBuilder); - link.setInlineEntity(inlineEntity); - } - } else { - throw new DeserializerException("Invalid value: " + jsonNode.getNodeType() - + " for expanded navigation property: " + navigationPropertyName, - MessageKeys.INVALID_VALUE_FOR_NAVIGATION_PROPERTY, navigationPropertyName); - } - return link; + final ExpandTreeBuilder childExpandBuilder = (expandBuilder != null) ? expandBuilder.expand(edmNavigationProperty) + : null; + EdmEntityType derivedEdmEntityType = (EdmEntityType) getDerivedType( + edmNavigationProperty.getType(), jsonNode); + if (jsonNode.isArray() && edmNavigationProperty.isCollection()) { + link.setType(Constants.ENTITY_SET_NAVIGATION_LINK_TYPE); + EntityCollection inlineEntitySet = new EntityCollection(); + inlineEntitySet.getEntities().addAll( + consumeEntitySetArray(derivedEdmEntityType, jsonNode, childExpandBuilder)); + link.setInlineEntitySet(inlineEntitySet); + } else if (!jsonNode.isArray() && (!jsonNode.isValueNode() || jsonNode.isNull()) + && !edmNavigationProperty.isCollection()) { + link.setType(Constants.ENTITY_NAVIGATION_LINK_TYPE); + if (!jsonNode.isNull()) { + Entity inlineEntity = consumeEntityNode(derivedEdmEntityType, (ObjectNode) jsonNode, childExpandBuilder); + link.setInlineEntity(inlineEntity); + } + } else { + throw new DeserializerException("Invalid value: " + jsonNode.getNodeType() + + " for expanded navigation property: " + navigationPropertyName, + MessageKeys.INVALID_VALUE_FOR_NAVIGATION_PROPERTY, navigationPropertyName); + } + return link; } private Link consumeBindingLink(final String key, final JsonNode jsonNode, final EdmEntityType edmEntityType) @@ -477,7 +476,7 @@ public class ODataJsonDeserializer implements ODataDeserializer { private void consumePropertySingleNode(final String name, final EdmType type, final boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, final boolean isUnicode, final EdmMapping mapping, final JsonNode jsonNode, final Property property) - throws DeserializerException { + throws DeserializerException { switch (type.getKind()) { case PRIMITIVE: case DEFINITION: @@ -504,7 +503,7 @@ public class ODataJsonDeserializer implements ODataDeserializer { private Object readComplexNode(final String name, final EdmType type, final boolean isNullable, final JsonNode jsonNode) - throws DeserializerException { + throws DeserializerException { // read and add all complex properties ComplexValue value = readComplexValue(name, type, isNullable, jsonNode); @@ -520,7 +519,7 @@ public class ODataJsonDeserializer implements ODataDeserializer { private void consumePropertyCollectionNode(final String name, final EdmType type, final boolean isNullable, final Integer maxLength, final Integer precision, final Integer scale, final boolean isUnicode, final EdmMapping mapping, final JsonNode jsonNode, final Property property) - throws DeserializerException { + throws DeserializerException { if (!jsonNode.isArray()) { throw new DeserializerException("Value for property: " + name + " must be an array but is not.", DeserializerException.MessageKeys.INVALID_JSON_TYPE_FOR_PROPERTY, name); @@ -537,10 +536,8 @@ public class ODataJsonDeserializer implements ODataDeserializer { isNullable, maxLength, precision, scale, isUnicode, mapping, arrayElement); valueArray.add(value); } - property.setValue(type.getKind() == EdmTypeKind.ENUM ? - ValueType.COLLECTION_ENUM : - ValueType.COLLECTION_PRIMITIVE, - valueArray); + property.setValue(type.getKind() == EdmTypeKind.ENUM ? ValueType.COLLECTION_ENUM : ValueType.COLLECTION_PRIMITIVE, + valueArray); break; case COMPLEX: while (iterator.hasNext()) { @@ -631,14 +628,10 @@ public class ODataJsonDeserializer implements ODataDeserializer { */ private Class getJavaClassForPrimitiveType(final EdmMapping mapping, final EdmPrimitiveType type) { final EdmPrimitiveType edmPrimitiveType = - type.getKind() == EdmTypeKind.ENUM ? - ((EdmEnumType) type).getUnderlyingType() : - type.getKind() == EdmTypeKind.DEFINITION ? - ((EdmTypeDefinition) type).getUnderlyingType() : - type; - return mapping == null || mapping.getMappedJavaClass() == null ? - edmPrimitiveType.getDefaultType() : - mapping.getMappedJavaClass(); + type.getKind() == EdmTypeKind.ENUM ? ((EdmEnumType) type).getUnderlyingType() : type + .getKind() == EdmTypeKind.DEFINITION ? ((EdmTypeDefinition) type).getUnderlyingType() : type; + return mapping == null || mapping.getMappedJavaClass() == null ? edmPrimitiveType.getDefaultType() : mapping + .getMappedJavaClass(); } /** @@ -831,7 +824,7 @@ public class ODataJsonDeserializer implements ODataDeserializer { String odataType = odataTypeNode.asText(); if (!odataType.isEmpty()) { odataType = odataType.substring(1); - + if (odataType.equalsIgnoreCase(edmType.getFullQualifiedName().getFullQualifiedNameAsString())) { return edmType; } else if (this.serviceMetadata == null) { @@ -839,14 +832,14 @@ public class ODataJsonDeserializer implements ODataDeserializer { "Failed to resolve Odata type " + odataType + " due to metadata is not available", DeserializerException.MessageKeys.UNKNOWN_CONTENT); } - + EdmStructuredType currentEdmType = null; - if(edmType instanceof EdmEntityType) { + if (edmType instanceof EdmEntityType) { currentEdmType = serviceMetadata.getEdm() - .getEntityType(new FullQualifiedName(odataType)); + .getEntityType(new FullQualifiedName(odataType)); } else { currentEdmType = serviceMetadata.getEdm() - .getComplexType(new FullQualifiedName(odataType)); + .getComplexType(new FullQualifiedName(odataType)); } if (!isAssignable(edmType, currentEdmType)) { throw new DeserializerException( diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataDeserializerDeepInsertTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataDeserializerDeepInsertTest.java index 8b87a125e..e684c0f90 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataDeserializerDeepInsertTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataDeserializerDeepInsertTest.java @@ -30,11 +30,63 @@ import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.Link; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.server.api.deserializer.DeserializerException; +import org.apache.olingo.server.api.deserializer.DeserializerResult; +import org.apache.olingo.server.api.uri.queryoption.ExpandItem; +import org.apache.olingo.server.api.uri.queryoption.ExpandOption; import org.apache.olingo.server.core.deserializer.AbstractODataDeserializerTest; import org.junit.Test; public class ODataDeserializerDeepInsertTest extends AbstractODataDeserializerTest { + @Test + public void unbalancedESAllPrim() throws Exception { + final DeserializerResult result = deserializeWithResult("UnbalancedESAllPrimFeed.json"); + ExpandOption root = result.getExpandTree(); + assertEquals(1, root.getExpandItems().size()); + + ExpandItem etTwoPrimManyLevel = root.getExpandItems().get(0); + assertEquals("NavPropertyETTwoPrimMany", etTwoPrimManyLevel.getResourcePath().getUriResourceParts().get(0) + .getSegmentValue()); + assertEquals(1, etTwoPrimManyLevel.getExpandOption().getExpandItems().size()); + + ExpandItem etAllPrimOneLevel = etTwoPrimManyLevel.getExpandOption().getExpandItems().get(0); + assertEquals("NavPropertyETAllPrimOne", etAllPrimOneLevel.getResourcePath().getUriResourceParts().get(0) + .getSegmentValue()); + assertEquals(1, etAllPrimOneLevel.getExpandOption().getExpandItems().size()); + + ExpandItem etTwoPrimOneLevel = etAllPrimOneLevel.getExpandOption().getExpandItems().get(0); + assertEquals("NavPropertyETTwoPrimOne", etTwoPrimOneLevel.getResourcePath().getUriResourceParts().get(0) + .getSegmentValue()); + assertNull(etTwoPrimOneLevel.getExpandOption()); + } + + @Test + public void unbalancedESAllPrim2() throws Exception { + final DeserializerResult result = deserializeWithResult("UnbalancedESAllPrimFeed2.json"); + ExpandOption root = result.getExpandTree(); + assertEquals(1, root.getExpandItems().size()); + + ExpandItem etTwoPrimManyLevel = root.getExpandItems().get(0); + assertEquals("NavPropertyETTwoPrimMany", etTwoPrimManyLevel.getResourcePath().getUriResourceParts().get(0) + .getSegmentValue()); + assertEquals(1, etTwoPrimManyLevel.getExpandOption().getExpandItems().size()); + + ExpandItem etAllPrimOneLevel = etTwoPrimManyLevel.getExpandOption().getExpandItems().get(0); + assertEquals("NavPropertyETAllPrimOne", etAllPrimOneLevel.getResourcePath().getUriResourceParts().get(0) + .getSegmentValue()); + assertEquals(2, etAllPrimOneLevel.getExpandOption().getExpandItems().size()); + + ExpandItem etTwoPrimOneLevel = etAllPrimOneLevel.getExpandOption().getExpandItems().get(0); + assertEquals("NavPropertyETTwoPrimMany", etTwoPrimOneLevel.getResourcePath().getUriResourceParts().get(0) + .getSegmentValue()); + assertNull(etTwoPrimOneLevel.getExpandOption()); + + etTwoPrimOneLevel = etAllPrimOneLevel.getExpandOption().getExpandItems().get(1); + assertEquals("NavPropertyETTwoPrimOne", etTwoPrimOneLevel.getResourcePath().getUriResourceParts().get(0) + .getSegmentValue()); + assertNull(etTwoPrimOneLevel.getExpandOption()); + } + @Test public void esAllPrimExpandedToOne() throws Exception { final Entity entity = deserialize("EntityESAllPrimExpandedNavPropertyETTwoPrimOne.json"); @@ -152,4 +204,10 @@ public class ODataDeserializerDeepInsertTest extends AbstractODataDeserializerTe return ODataJsonDeserializerEntityTest.deserialize(getFileAsStream(resourceName), "ETAllPrim", ContentType.JSON); } + + private DeserializerResult deserializeWithResult(final String resourceName) throws IOException, + DeserializerException { + return ODataJsonDeserializerEntityTest.deserializeWithResult(getFileAsStream(resourceName), + "ETAllPrim", ContentType.JSON); + } } diff --git a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java index 02453e6b6..6b46ffdac 100644 --- a/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java +++ b/lib/server-test/src/test/java/org/apache/olingo/server/core/deserializer/json/ODataJsonDeserializerEntityTest.java @@ -49,6 +49,7 @@ import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.core.edm.primitivetype.EdmDate; import org.apache.olingo.server.api.OData; import org.apache.olingo.server.api.deserializer.DeserializerException; +import org.apache.olingo.server.api.deserializer.DeserializerResult; import org.apache.olingo.server.api.deserializer.ODataDeserializer; import org.apache.olingo.server.core.deserializer.AbstractODataDeserializerTest; import org.junit.Assert; @@ -1347,6 +1348,12 @@ public class ODataJsonDeserializerEntityTest extends AbstractODataDeserializerTe .entity(stream, edm.getEntityType(new FullQualifiedName(NAMESPACE, entityTypeName))) .getEntity(); } + + protected static DeserializerResult deserializeWithResult(final InputStream stream, final String entityTypeName, + final ContentType contentType) throws DeserializerException { + return OData.newInstance().createDeserializer(contentType, metadata) + .entity(stream, edm.getEntityType(new FullQualifiedName(NAMESPACE, entityTypeName))); + } private static Entity deserialize(final String entityString, final String entityTypeName, final ContentType contentType) throws DeserializerException { diff --git a/lib/server-test/src/test/resources/UnbalancedESAllPrimFeed.json b/lib/server-test/src/test/resources/UnbalancedESAllPrimFeed.json new file mode 100644 index 000000000..d3fa5508d --- /dev/null +++ b/lib/server-test/src/test/resources/UnbalancedESAllPrimFeed.json @@ -0,0 +1,23 @@ +{ + "@odata.context": "$metadata#ESAllPrim\/$entity", + "@odata.metadataEtag": "W\/\"4efd6576-89c0-487c-8d6c-584e2acbae16\"", + "PropertyInt16": 1, + "NavPropertyETTwoPrimMany": [ + { + "PropertyInt16": 2, + "NavPropertyETAllPrimOne": { + "PropertyInt16": 3 + } + }, + { + "PropertyInt16": 2, + "NavPropertyETAllPrimOne": { + "PropertyInt16": 3, + "NavPropertyETTwoPrimOne": { + "PropertyInt16": 32766, + "PropertyString": "Innermost Entry" + } + } + } + ] +} \ No newline at end of file diff --git a/lib/server-test/src/test/resources/UnbalancedESAllPrimFeed2.json b/lib/server-test/src/test/resources/UnbalancedESAllPrimFeed2.json new file mode 100644 index 000000000..678fd3ae4 --- /dev/null +++ b/lib/server-test/src/test/resources/UnbalancedESAllPrimFeed2.json @@ -0,0 +1,24 @@ +{ + "@odata.context": "$metadata#ESAllPrim\/$entity", + "@odata.metadataEtag": "W\/\"4efd6576-89c0-487c-8d6c-584e2acbae16\"", + "PropertyInt16": 1, + "NavPropertyETTwoPrimMany": [ + { + "PropertyInt16": 2, + "NavPropertyETAllPrimOne": { + "PropertyInt16": 3, + "NavPropertyETTwoPrimMany": [] + } + }, + { + "PropertyInt16": 2, + "NavPropertyETAllPrimOne": { + "PropertyInt16": 3, + "NavPropertyETTwoPrimOne": { + "PropertyInt16": 32766, + "PropertyString": "Innermost Entry" + } + } + } + ] +} \ No newline at end of file