Repository: olingo-odata2 Updated Branches: refs/heads/master 7236755aa -> 9142cbd34
[OLINGO-1051] Provide flexible inline property serialization Contribution by Ramya in https://issues.apache.org/jira/browse/OLINGO-1051 Project: http://git-wip-us.apache.org/repos/asf/olingo-odata2/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata2/commit/9142cbd3 Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata2/tree/9142cbd3 Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata2/diff/9142cbd3 Branch: refs/heads/master Commit: 9142cbd348ca348d92e80ad56980174b5348b7ae Parents: 7236755 Author: Christian Amend <[email protected]> Authored: Thu Nov 24 11:40:56 2016 +0100 Committer: Christian Amend <[email protected]> Committed: Thu Nov 24 11:40:56 2016 +0100 ---------------------------------------------------------------------- .../api/ep/EntityProviderWriteProperties.java | 19 + .../ep/producer/AtomEntryEntityProducer.java | 46 ++- .../producer/JsonCollectionEntityProducer.java | 2 +- .../ep/producer/JsonEntryEntityProducer.java | 46 ++- .../ep/producer/JsonPropertyEntityProducer.java | 20 +- .../ep/producer/XmlPropertyEntityProducer.java | 5 + .../ep/ODataEntityProviderPropertiesTest.java | 3 + .../core/ep/producer/AtomEntryProducerTest.java | 108 ++++++ .../core/ep/producer/AtomFeedProducerTest.java | 347 +++++++++++++++++++ .../producer/JsonEntryEntityProducerTest.java | 56 ++- .../ep/producer/JsonFeedEntityProducerTest.java | 309 +++++++++++++++++ .../olingo/odata2/testutil/mock/EdmMock.java | 10 + 12 files changed, 938 insertions(+), 33 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProviderWriteProperties.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProviderWriteProperties.java b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProviderWriteProperties.java index a2485a4..354d0f5 100644 --- a/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProviderWriteProperties.java +++ b/odata2-lib/odata-api/src/main/java/org/apache/olingo/odata2/api/ep/EntityProviderWriteProperties.java @@ -49,9 +49,18 @@ public class EntityProviderWriteProperties { private boolean isResponsePayload = true; private boolean includeMetadataInContentOnly = false; + private boolean isDataBasedPropertySerialization = false; private EntityProviderWriteProperties() {} + /** + * Returns true if the payload has dynamic properties i.e. every entry has different property list + * @return + */ + public final boolean isDataBasedPropertySerialization() { + return isDataBasedPropertySerialization; + } + public final boolean isOmitETag() { return omitETag; } @@ -155,6 +164,15 @@ public class EntityProviderWriteProperties { private final EntityProviderWriteProperties properties = new EntityProviderWriteProperties(); /** + * @param setting if payload has dynamic property + */ + public final ODataEntityProviderPropertiesBuilder isDataBasedPropertySerialization + (boolean isDataBasedPropertySerialization) { + properties.isDataBasedPropertySerialization = isDataBasedPropertySerialization; + return this; + } + + /** * @param includeSimplePropertyType true to include simple property type information in the payload */ public final ODataEntityProviderPropertiesBuilder includeSimplePropertyType( @@ -293,6 +311,7 @@ public class EntityProviderWriteProperties { this.properties.validatingFacets = properties.validatingFacets; this.properties.isResponsePayload = properties.isResponsePayload; this.properties.includeMetadataInContentOnly = properties.includeMetadataInContentOnly; + this.properties.isDataBasedPropertySerialization = properties.isDataBasedPropertySerialization; return this; } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryEntityProducer.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryEntityProducer.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryEntityProducer.java index 1c82a38..ea00c6f 100644 --- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryEntityProducer.java +++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryEntityProducer.java @@ -584,27 +584,49 @@ public class AtomEntryEntityProducer { private void appendProperties(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException { try { - List<String> propertyNames = eia.getSelectedPropertyNames(); - if (!propertyNames.isEmpty()) { - writer.writeStartElement(Edm.NAMESPACE_M_2007_08, FormatXml.M_PROPERTIES); - - for (String propertyName : propertyNames) { - EntityPropertyInfo propertyInfo = eia.getPropertyInfo(propertyName); - - if (isNotMappedViaCustomMapping(propertyInfo)) { - Object value = data.get(propertyName); - XmlPropertyEntityProducer aps = new XmlPropertyEntityProducer(properties); - aps.append(writer, propertyInfo.getName(), propertyInfo, value); + if (properties.isDataBasedPropertySerialization()) { + if (!data.isEmpty()) { + writer.writeStartElement(Edm.NAMESPACE_M_2007_08, FormatXml.M_PROPERTIES); + for (String propertyName : eia.getPropertyNames()) { + if (data.containsKey(propertyName)) { + appendPropertyNameValue(writer, eia, data, propertyName); + } } + writer.writeEndElement(); } + } else { + List<String> propertyNames = eia.getSelectedPropertyNames(); + if (!propertyNames.isEmpty()) { + writer.writeStartElement(Edm.NAMESPACE_M_2007_08, FormatXml.M_PROPERTIES); - writer.writeEndElement(); + for (String propertyName : propertyNames) { + appendPropertyNameValue(writer, eia, data, propertyName); + } + writer.writeEndElement(); + } } } catch (XMLStreamException e) { throw new EntityProviderProducerException(EntityProviderException.COMMON, e); } } + /** + * @param writer + * @param eia + * @param data + * @param propertyName + * @throws EntityProviderException + */ + private void appendPropertyNameValue(final XMLStreamWriter writer, final EntityInfoAggregator eia, + final Map<String, Object> data, String propertyName) throws EntityProviderException { + EntityPropertyInfo propertyInfo = eia.getPropertyInfo(propertyName); + if (isNotMappedViaCustomMapping(propertyInfo)) { + Object value = data.get(propertyName); + XmlPropertyEntityProducer aps = new XmlPropertyEntityProducer(properties); + aps.append(writer, propertyInfo.getName(), propertyInfo, value); + } + } + private boolean isNotMappedViaCustomMapping(final EntityPropertyInfo propertyInfo) { EdmCustomizableFeedMappings customMapping = propertyInfo.getCustomMapping(); if (customMapping != null && customMapping.isFcKeepInContent() != null) { http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonCollectionEntityProducer.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonCollectionEntityProducer.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonCollectionEntityProducer.java index 0c25410..d03b9d3 100644 --- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonCollectionEntityProducer.java +++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonCollectionEntityProducer.java @@ -63,7 +63,7 @@ public class JsonCollectionEntityProducer { } else { jsonStreamWriter.separator(); } - JsonPropertyEntityProducer.appendPropertyValue(jsonStreamWriter, propertyInfo, item, true); + JsonPropertyEntityProducer.appendPropertyValue(jsonStreamWriter, propertyInfo, item, true, false); } jsonStreamWriter.endArray(); http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducer.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducer.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducer.java index 6615935..ce9a5bb 100644 --- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducer.java +++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducer.java @@ -193,23 +193,43 @@ public class JsonEntryEntityProducer { // properties boolean omitComma = !containsMetadata; - for (final String propertyName : type.getPropertyNames()) { - if (entityInfo.getSelectedPropertyNames().contains(propertyName)) { - if (omitComma) { - omitComma = false; - } else { - jsonStreamWriter.separator(); - } - jsonStreamWriter.name(propertyName); - - JsonPropertyEntityProducer.appendPropertyValue(jsonStreamWriter, - entityInfo.getPropertyInfo(propertyName), - data.get(propertyName), - properties.isValidatingFacets()); + List<String> propertyNames = type.getPropertyNames(); + for (final String propertyName : propertyNames) { + if (properties.isDataBasedPropertySerialization() && ((Map<?,?>)data).containsKey(propertyName)) { + omitComma = appendPropertyNameValue(entityInfo, data, omitComma, propertyName); + } else if (!properties.isDataBasedPropertySerialization() && entityInfo.getSelectedPropertyNames() + .contains(propertyName)) { + omitComma = appendPropertyNameValue(entityInfo, data, omitComma, propertyName); } } } + /** + * @param entityInfo + * @param data + * @param omitComma + * @param propertyName + * @return + * @throws IOException + * @throws EdmException + * @throws EntityProviderException + */ + private boolean appendPropertyNameValue(final EntityInfoAggregator entityInfo, final Map<String, Object> data, + boolean omitComma, String propertyName) throws IOException, EdmException, EntityProviderException { + if (omitComma) { + omitComma = false; + } else { + jsonStreamWriter.separator(); + } + jsonStreamWriter.name(propertyName); + + JsonPropertyEntityProducer.appendPropertyValue(jsonStreamWriter, + entityInfo.getPropertyInfo(propertyName), + data.get(propertyName), + properties.isValidatingFacets(), properties.isDataBasedPropertySerialization()); + return omitComma; + } + private void writeMetadata(final EntityInfoAggregator entityInfo, final Map<String, Object> data, final EdmEntityType type) throws IOException, EntityProviderException, EdmException { if (properties.getServiceRoot() == null) { http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonPropertyEntityProducer.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonPropertyEntityProducer.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonPropertyEntityProducer.java index 00ab712..9de1f9f 100644 --- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonPropertyEntityProducer.java +++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/JsonPropertyEntityProducer.java @@ -54,7 +54,7 @@ public class JsonPropertyEntityProducer { jsonStreamWriter.name(propertyInfo.getName()); appendPropertyValue(jsonStreamWriter, propertyInfo.isComplex() ? (EntityComplexPropertyInfo) propertyInfo - : propertyInfo, value, true); + : propertyInfo, value, true, false); jsonStreamWriter.endObject() .endObject(); @@ -68,19 +68,27 @@ public class JsonPropertyEntityProducer { protected static void appendPropertyValue(final JsonStreamWriter jsonStreamWriter, final EntityPropertyInfo propertyInfo, final Object value, - boolean validatingFacets) throws IOException, EdmException, + boolean validatingFacets, + boolean isDataBasedPropertySerialization) throws IOException, EdmException, EntityProviderException { if (propertyInfo.isComplex()) { if (value == null || value instanceof Map<?, ?>) { jsonStreamWriter.beginObject(); appendPropertyMetadata(jsonStreamWriter, propertyInfo.getType()); - for (final EntityPropertyInfo childPropertyInfo : ((EntityComplexPropertyInfo) propertyInfo).getPropertyInfos()) - { - jsonStreamWriter.separator(); + if (value == null && isDataBasedPropertySerialization) { + jsonStreamWriter.endObject(); + return; + } + for (final EntityPropertyInfo childPropertyInfo : ((EntityComplexPropertyInfo) propertyInfo) + .getPropertyInfos()) { final String name = childPropertyInfo.getName(); + if (isDataBasedPropertySerialization && !((Map<?,?>)value).containsKey(name)) { + continue; + } + jsonStreamWriter.separator(); jsonStreamWriter.name(name); appendPropertyValue(jsonStreamWriter, childPropertyInfo, - value == null ? null : ((Map<?, ?>) value).get(name), validatingFacets); + value == null ? null : ((Map<?, ?>) value).get(name), validatingFacets, isDataBasedPropertySerialization); } jsonStreamWriter.endObject(); } else { http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/XmlPropertyEntityProducer.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/XmlPropertyEntityProducer.java b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/XmlPropertyEntityProducer.java index 587748d..293545d 100644 --- a/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/XmlPropertyEntityProducer.java +++ b/odata2-lib/odata-core/src/main/java/org/apache/olingo/odata2/core/ep/producer/XmlPropertyEntityProducer.java @@ -45,9 +45,11 @@ public class XmlPropertyEntityProducer { private final boolean includeSimplePropertyType; private final boolean validateFacets; + private boolean isDataBasedPropertySerialization = false; public XmlPropertyEntityProducer(final EntityProviderWriteProperties writeProperties) { this(writeProperties.isIncludeSimplePropertyType(), writeProperties.isValidatingFacets()); + isDataBasedPropertySerialization = writeProperties.isDataBasedPropertySerialization(); } public XmlPropertyEntityProducer(final boolean includeSimplePropertyType, final boolean validateFacets) { @@ -148,6 +150,9 @@ public class XmlPropertyEntityProducer { writer.writeAttribute(Edm.NAMESPACE_M_2007_08, FormatXml.ATOM_TYPE, getFqnTypeName(propertyInfo)); List<EntityPropertyInfo> propertyInfos = propertyInfo.getPropertyInfos(); for (EntityPropertyInfo childPropertyInfo : propertyInfos) { + if (isDataBasedPropertySerialization && !((Map<?,?>)value).containsKey(childPropertyInfo.getName())) { + continue; + } Object childValue = extractChildValue(value, childPropertyInfo.getName()); append(writer, childPropertyInfo.getName(), childPropertyInfo, childValue); } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/ODataEntityProviderPropertiesTest.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/ODataEntityProviderPropertiesTest.java b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/ODataEntityProviderPropertiesTest.java index 07f71c4..241828c 100644 --- a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/ODataEntityProviderPropertiesTest.java +++ b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/ODataEntityProviderPropertiesTest.java @@ -77,6 +77,7 @@ public class ODataEntityProviderPropertiesTest extends BaseTest { assertFalse(properties.isOmitETag()); assertFalse(properties.isIncludeMetadataInContentOnly()); assertTrue(properties.isResponsePayload()); + assertFalse(properties.isDataBasedPropertySerialization()); } @Test @@ -102,6 +103,7 @@ public class ODataEntityProviderPropertiesTest extends BaseTest { .omitETag(true) .includeMetadataInContentOnly(true) .responsePayload(true) + .isDataBasedPropertySerialization(true) .build(); assertEquals("Wrong amount of callbacks.", 1, properties.getCallbacks().size()); @@ -120,6 +122,7 @@ public class ODataEntityProviderPropertiesTest extends BaseTest { assertTrue("includeMetadataInContentOnly should be set", properties.isIncludeMetadataInContentOnly()); assertTrue("responsePayload flag should be set", properties.isResponsePayload()); + assertTrue(properties.isDataBasedPropertySerialization()); } @Test http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryProducerTest.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryProducerTest.java b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryProducerTest.java index 29ce3af..0a2cd48 100644 --- a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryProducerTest.java +++ b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/AtomEntryProducerTest.java @@ -42,6 +42,7 @@ import javax.xml.stream.XMLStreamException; import junit.framework.Assert; +import org.apache.olingo.odata2.api.ODataCallback; import org.apache.olingo.odata2.api.edm.Edm; import org.apache.olingo.odata2.api.edm.EdmConcurrencyMode; import org.apache.olingo.odata2.api.edm.EdmCustomizableFeedMappings; @@ -54,16 +55,27 @@ import org.apache.olingo.odata2.api.edm.EdmTargetPath; import org.apache.olingo.odata2.api.edm.EdmTyped; import org.apache.olingo.odata2.api.ep.EntityProviderException; import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties; +import org.apache.olingo.odata2.api.ep.callback.OnWriteEntryContent; +import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackContext; +import org.apache.olingo.odata2.api.ep.callback.WriteEntryCallbackResult; +import org.apache.olingo.odata2.api.exception.ODataApplicationException; import org.apache.olingo.odata2.api.exception.ODataException; import org.apache.olingo.odata2.api.exception.ODataMessageException; import org.apache.olingo.odata2.api.processor.ODataResponse; +import org.apache.olingo.odata2.api.rt.RuntimeDelegate; import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode; +import org.apache.olingo.odata2.api.uri.PathSegment; +import org.apache.olingo.odata2.api.uri.UriInfo; +import org.apache.olingo.odata2.core.ODataPathSegmentImpl; import org.apache.olingo.odata2.core.commons.ContentType; import org.apache.olingo.odata2.core.ep.AbstractProviderTest; import org.apache.olingo.odata2.core.ep.AtomEntityProvider; import org.apache.olingo.odata2.core.ep.EntityProviderProducerException; +import org.apache.olingo.odata2.core.uri.ExpandSelectTreeCreator; +import org.apache.olingo.odata2.core.uri.UriParserImpl; import org.apache.olingo.odata2.testutil.helper.StringHelper; import org.apache.olingo.odata2.testutil.helper.XMLUnitHelper; +import org.apache.olingo.odata2.testutil.mock.EdmTestProvider; import org.apache.olingo.odata2.testutil.mock.MockFacade; import org.custommonkey.xmlunit.SimpleNamespaceContext; import org.custommonkey.xmlunit.XMLUnit; @@ -73,6 +85,8 @@ import org.xml.sax.SAXException; public class AtomEntryProducerTest extends AbstractProviderTest { + private String buildingXPathString = "/a:entry/a:link[@href=\"Rooms('1')/nr_Building\" and @title='nr_Building']"; + public AtomEntryProducerTest(final StreamWriterImplType type) { super(type); } @@ -1262,4 +1276,98 @@ public class AtomEntryProducerTest extends AbstractProviderTest { private void verifyTagOrdering(final String xmlString, final String... toCheckTags) { XMLUnitHelper.verifyTagOrdering(xmlString, toCheckTags); } + + @Test + public void unbalancedPropertyEntryWithInlineEntry() throws Exception { + ExpandSelectTreeNode selectTree = getSelectExpandTree("Rooms('1')", "nr_Building", "nr_Building"); + + Map<String, Object> roomData = new HashMap<String, Object>(); + roomData.put("Id", "1"); + roomData.put("Name", "Neu Schwanstein"); + roomData.put("Seats", new Integer(20)); + + class EntryCallback implements OnWriteEntryContent { + @Override + public WriteEntryCallbackResult retrieveEntryResult(final WriteEntryCallbackContext context) + throws ODataApplicationException { + Map<String, Object> buildingData = new HashMap<String, Object>(); + buildingData.put("Id", "1"); + buildingData.put("Name", "Building1"); + + WriteEntryCallbackResult result = new WriteEntryCallbackResult(); + result.setEntryData(buildingData); + EntityProviderWriteProperties inlineProperties = + EntityProviderWriteProperties.serviceRoot(BASE_URI).expandSelectTree( + context.getCurrentExpandSelectTreeNode()).build(); + result.setInlineProperties(inlineProperties); + return result; + } + } + EntryCallback callback = new EntryCallback(); + Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>(); + callbacks.put("nr_Building", callback); + + EntityProviderWriteProperties properties = + EntityProviderWriteProperties.serviceRoot(BASE_URI).expandSelectTree(selectTree).callbacks(callbacks). + isDataBasedPropertySerialization(true).build(); + AtomEntityProvider provider = createAtomEntityProvider(); + ODataResponse response = + provider.writeEntry(MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms"), roomData, + properties); + + String xmlString = verifyResponse(response); + assertXpathNotExists("/a:entry/m:properties", xmlString); + assertXpathExists("/a:entry/a:link", xmlString); + verifyBuilding(buildingXPathString, xmlString); + } + + private ExpandSelectTreeNode getSelectExpandTree(final String pathSegment, final String selectString, + final String expandString) throws Exception { + + Edm edm = RuntimeDelegate.createEdm(new EdmTestProvider()); + UriParserImpl uriParser = new UriParserImpl(edm); + + List<PathSegment> pathSegments = new ArrayList<PathSegment>(); + pathSegments.add(new ODataPathSegmentImpl(pathSegment, null)); + + Map<String, String> queryParameters = new HashMap<String, String>(); + if (selectString != null) { + queryParameters.put("$select", selectString); + } + if (expandString != null) { + queryParameters.put("$expand", expandString); + } + UriInfo uriInfo = uriParser.parse(pathSegments, queryParameters); + + ExpandSelectTreeCreator expandSelectTreeCreator = + new ExpandSelectTreeCreator(uriInfo.getSelect(), uriInfo.getExpand()); + ExpandSelectTreeNode expandSelectTree = expandSelectTreeCreator.create(); + assertNotNull(expandSelectTree); + return expandSelectTree; + } + + private void verifyBuilding(final String path, final String xmlString) throws XpathException, IOException, + SAXException { + assertXpathExists(path, xmlString); + assertXpathExists(path + "/m:inline", xmlString); + + assertXpathExists(path + "/m:inline/a:entry[@xml:base='" + BASE_URI + "']", xmlString); + assertXpathExists(path + "/m:inline/a:entry", xmlString); + assertXpathExists(path + "/m:inline/a:entry/a:id", xmlString); + assertXpathExists(path + "/m:inline/a:entry/a:title", xmlString); + assertXpathExists(path + "/m:inline/a:entry/a:updated", xmlString); + + assertXpathExists(path + "/m:inline/a:entry/a:category", xmlString); + assertXpathExists(path + "/m:inline/a:entry/a:link", xmlString); + + assertXpathExists(path + "/m:inline/a:entry/a:content", xmlString); + assertXpathExists(path + "/m:inline/a:entry/a:content/m:properties", xmlString); + assertXpathExists(path + "/m:inline/a:entry/a:content/m:properties/d:Id", xmlString); + assertXpathExists(path + "/m:inline/a:entry/a:content/m:properties/d:Name", xmlString); + + assertXpathExists("/a:entry/a:content/m:properties/d:Id", xmlString); + assertXpathExists("/a:entry/a:content/m:properties/d:Name", xmlString); + assertXpathExists("/a:entry/a:content/m:properties/d:Seats", xmlString); + + } } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/AtomFeedProducerTest.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/AtomFeedProducerTest.java b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/AtomFeedProducerTest.java index 37432dd..8487bbc 100644 --- a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/AtomFeedProducerTest.java +++ b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/AtomFeedProducerTest.java @@ -22,6 +22,8 @@ import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo; import static org.custommonkey.xmlunit.XMLAssert.assertXpathExists; import static org.custommonkey.xmlunit.XMLAssert.assertXpathNotExists; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -29,25 +31,52 @@ import static org.mockito.Mockito.when; import java.io.IOException; import java.io.InputStream; import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.olingo.odata2.api.ODataCallback; import org.apache.olingo.odata2.api.commons.InlineCount; +import org.apache.olingo.odata2.api.edm.Edm; import org.apache.olingo.odata2.api.edm.EdmEntitySet; import org.apache.olingo.odata2.api.ep.EntityProviderException; +import org.apache.olingo.odata2.api.ep.EntityProviderReadProperties; import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties; +import org.apache.olingo.odata2.api.ep.callback.OnWriteFeedContent; +import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackContext; +import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackResult; +import org.apache.olingo.odata2.api.ep.entry.ODataEntry; +import org.apache.olingo.odata2.api.ep.feed.ODataFeed; +import org.apache.olingo.odata2.api.exception.ODataApplicationException; import org.apache.olingo.odata2.api.processor.ODataResponse; +import org.apache.olingo.odata2.api.rt.RuntimeDelegate; +import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode; +import org.apache.olingo.odata2.api.uri.PathSegment; +import org.apache.olingo.odata2.api.uri.UriInfo; import org.apache.olingo.odata2.api.uri.info.GetEntitySetUriInfo; +import org.apache.olingo.odata2.core.ODataPathSegmentImpl; import org.apache.olingo.odata2.core.ep.AbstractProviderTest; import org.apache.olingo.odata2.core.ep.AtomEntityProvider; +import org.apache.olingo.odata2.core.ep.EntityProviderProducerException; +import org.apache.olingo.odata2.core.ep.consumer.XmlEntityConsumer; +import org.apache.olingo.odata2.core.uri.ExpandSelectTreeCreator; +import org.apache.olingo.odata2.core.uri.UriParserImpl; import org.apache.olingo.odata2.testutil.helper.StringHelper; +import org.apache.olingo.odata2.testutil.mock.EdmTestProvider; import org.apache.olingo.odata2.testutil.mock.MockFacade; +import org.custommonkey.xmlunit.exceptions.XpathException; import org.junit.Before; import org.junit.Test; +import org.xml.sax.SAXException; /** * */ public class AtomFeedProducerTest extends AbstractProviderTest { + private String employeeXPathString = "/a:entry/a:link[@href=\"Rooms('1')/nr_Employees\" and @title='nr_Employees']"; + public AtomFeedProducerTest(final StreamWriterImplType type) { super(type); } @@ -215,4 +244,322 @@ public class AtomFeedProducerTest extends AbstractProviderTest { assertXpathExists("/a:feed/a:entry[103]", xmlString); } + @Test + public void unbalancedPropertyFeed() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> originalData = createData(true); + final ODataResponse response = new AtomEntityProvider().writeFeed(entitySet, originalData, + EntityProviderWriteProperties.serviceRoot(BASE_URI).isDataBasedPropertySerialization(true).build()); + + EntityProviderReadProperties readProperties = EntityProviderReadProperties.init().mergeSemantic(false).build(); + XmlEntityConsumer consumer = new XmlEntityConsumer(); + ODataFeed feed = consumer.readFeed(entitySet, (InputStream) response.getEntity(), readProperties); + + compareList(originalData, feed.getEntries()); + } + + @Test + public void unbalancedPropertyFeedWithInvalidProperty() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> originalData = createDataWithInvalidProperty(true); + final ODataResponse response = new AtomEntityProvider().writeFeed(entitySet, originalData, + EntityProviderWriteProperties.serviceRoot(BASE_URI).isDataBasedPropertySerialization(true).build()); + + EntityProviderReadProperties readProperties = EntityProviderReadProperties.init().mergeSemantic(false).build(); + XmlEntityConsumer consumer = new XmlEntityConsumer(); + ODataFeed feed = consumer.readFeed(entitySet, (InputStream) response.getEntity(), readProperties); + originalData.get(0).remove("Address"); + compareList(originalData, feed.getEntries()); + } + + @Test(expected = EntityProviderProducerException.class) + public void unbalancedPropertyFeedWithNullKey() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> originalData = createDataWithKeyNull(true); + new AtomEntityProvider().writeFeed(entitySet, originalData, + EntityProviderWriteProperties.serviceRoot(BASE_URI).isDataBasedPropertySerialization(true).build()); + } + + @Test(expected = EntityProviderProducerException.class) + public void unbalancedPropertyFeedWithoutKeys() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> originalData = createDataWithoutKey(true); + new AtomEntityProvider().writeFeed(entitySet, originalData, + EntityProviderWriteProperties.serviceRoot(BASE_URI).isDataBasedPropertySerialization(true).build()); + } + + @Test(expected = EntityProviderProducerException.class) + public void unbalancedPropertyFeedWithEmptyData() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> feedData = new ArrayList<Map<String, Object>>(); + Map<String, Object> entryData = new HashMap<String, Object>(); + feedData.add(entryData); + new AtomEntityProvider().writeFeed(entitySet, feedData, + EntityProviderWriteProperties.serviceRoot(BASE_URI).isDataBasedPropertySerialization(true).build()); + } + + @Test + public void unbalancedPropertyFeedWithSelect() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> originalData = createData(true); + List<String> selectedPropertyNames = new ArrayList<String>(); + selectedPropertyNames.add("Id"); + selectedPropertyNames.add("Location"); + ExpandSelectTreeNode select = + ExpandSelectTreeNode.entitySet(entitySet).selectedProperties(selectedPropertyNames).build(); + + ODataResponse response = new AtomEntityProvider().writeFeed(entitySet, originalData, + EntityProviderWriteProperties.serviceRoot(BASE_URI).expandSelectTree(select). + isDataBasedPropertySerialization(true).build()); + + EntityProviderReadProperties readProperties = EntityProviderReadProperties.init().mergeSemantic(false).build(); + XmlEntityConsumer consumer = new XmlEntityConsumer(); + ODataFeed feed = consumer.readFeed(entitySet, (InputStream) response.getEntity(), readProperties); + + compareList(originalData, feed.getEntries()); + } + + @Test + public void unbalancedPropertyEntryWithInlineFeed() throws Exception { + ExpandSelectTreeNode selectTree = getSelectExpandTree("Rooms('1')", "nr_Employees", "nr_Employees"); + + Map<String, Object> roomData = new HashMap<String, Object>(); + roomData.put("Id", "1"); + roomData.put("Name", "Neu Schwanstein"); + roomData.put("Seats", new Integer(20)); + + class EntryCallback implements OnWriteFeedContent { + @Override + public WriteFeedCallbackResult retrieveFeedResult(final WriteFeedCallbackContext context) + throws ODataApplicationException { + List<Map<String, Object>> listData = new ArrayList<Map<String, Object>>(); + Map<String, Object> data = new HashMap<String, Object>(); + data.put("EmployeeId", "1"); + data.put("EmployeeName", "EmpName1"); + data.put("RoomId", "1"); + listData.add(data); + + data = new HashMap<String, Object>(); + data.put("EmployeeId", "1"); + data.put("RoomId", "1"); + listData.add(data); + + WriteFeedCallbackResult result = new WriteFeedCallbackResult(); + result.setFeedData(listData); + EntityProviderWriteProperties inlineProperties = + EntityProviderWriteProperties.serviceRoot(BASE_URI).expandSelectTree( + context.getCurrentExpandSelectTreeNode()).build(); + result.setInlineProperties(inlineProperties); + return result; + } + } + EntryCallback callback = new EntryCallback(); + Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>(); + callbacks.put("nr_Employees", callback); + + EntityProviderWriteProperties properties = + EntityProviderWriteProperties.serviceRoot(BASE_URI).expandSelectTree(selectTree).callbacks(callbacks). + isDataBasedPropertySerialization(true).build(); + AtomEntityProvider provider = createAtomEntityProvider(); + ODataResponse response = + provider.writeEntry(MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms"), roomData, + properties); + + String xmlString = verifyResponse(response); + assertXpathNotExists("/a:entry/m:properties", xmlString); + assertXpathExists("/a:entry/a:link", xmlString); + verifyEmployees(employeeXPathString, xmlString); + } + + private ExpandSelectTreeNode getSelectExpandTree(final String pathSegment, final String selectString, + final String expandString) throws Exception { + + Edm edm = RuntimeDelegate.createEdm(new EdmTestProvider()); + UriParserImpl uriParser = new UriParserImpl(edm); + + List<PathSegment> pathSegments = new ArrayList<PathSegment>(); + pathSegments.add(new ODataPathSegmentImpl(pathSegment, null)); + + Map<String, String> queryParameters = new HashMap<String, String>(); + if (selectString != null) { + queryParameters.put("$select", selectString); + } + if (expandString != null) { + queryParameters.put("$expand", expandString); + } + UriInfo uriInfo = uriParser.parse(pathSegments, queryParameters); + + ExpandSelectTreeCreator expandSelectTreeCreator = + new ExpandSelectTreeCreator(uriInfo.getSelect(), uriInfo.getExpand()); + ExpandSelectTreeNode expandSelectTree = expandSelectTreeCreator.create(); + assertNotNull(expandSelectTree); + return expandSelectTree; + } + + private void verifyEmployees(final String path, final String xmlString) throws XpathException, IOException, + SAXException { + assertXpathExists(path, xmlString); + assertXpathExists(path + "/m:inline", xmlString); + + assertXpathExists(path + "/m:inline/a:feed[@xml:base='" + BASE_URI + "']", xmlString); + assertXpathExists(path + "/m:inline/a:feed/a:entry", xmlString); + assertXpathExists(path + "/m:inline/a:feed/a:entry/a:id", xmlString); + assertXpathExists(path + "/m:inline/a:feed/a:entry/a:title", xmlString); + assertXpathExists(path + "/m:inline/a:feed/a:entry/a:updated", xmlString); + + assertXpathExists(path + "/m:inline/a:feed/a:entry/a:category", xmlString); + assertXpathExists(path + "/m:inline/a:feed/a:entry/a:link", xmlString); + + assertXpathExists(path + "/m:inline/a:feed/a:entry/a:content", xmlString); + assertXpathExists(path + "/m:inline/a:feed/a:entry/m:properties", xmlString); + assertXpathExists(path + "/m:inline/a:feed/a:entry/m:properties/d:EmployeeId", xmlString); + assertXpathExists(path + "/m:inline/a:feed/a:entry/m:properties/d:EmployeeName", xmlString); + assertXpathExists(path + "/m:inline/a:feed/a:entry/m:properties/d:RoomId", xmlString); + + assertXpathExists("/a:entry/a:content/m:properties/d:Id", xmlString); + assertXpathExists("/a:entry/a:content/m:properties/d:Name", xmlString); + assertXpathExists("/a:entry/a:content/m:properties/d:Seats", xmlString); + + } + + private void compareList(List<Map<String, Object>> expectedList, List<ODataEntry> actualList) { + assertEquals(expectedList.size(), actualList.size()); + + for (int i = 0; i < expectedList.size(); i++) { + Map<String, Object> expected = expectedList.get(i); + Map<String, Object> actual = actualList.get(i).getProperties(); + compareMap(i, expected, actual); + } + } + + @SuppressWarnings("unchecked") + private void compareMap(int index, Map<String, Object> expected, Map<String, Object> actual) { + + assertEquals("Entry: " + index + " does not contain the same amount of properties", expected.size(), + actual.size()); + for (Map.Entry<String, Object> entry : expected.entrySet()) { + String key = entry.getKey(); + assertTrue("Entry " + index + " should contain key: " + key, actual.containsKey(key)); + + if (entry.getValue() instanceof Map<?, ?>) { + assertTrue("Entry " + index + " Value: " + key + " should be a map", actual.get(key) instanceof Map<?, ?>); + compareMap(index, (Map<String, Object>) entry.getValue(), (Map<String, Object>) actual.get(key)); + } else { + assertEquals("Entry: " + index + " values are not the same: " + key, entry.getValue(), actual.get(key)); + } + } + } + + private List<Map<String, Object>> createData(boolean includeKeys) { + List<Map<String, Object>> feedData = new ArrayList<Map<String, Object>>(); + Map<String, Object> entryData = new HashMap<String, Object>(); + entryData.put("Id", "1"); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", "2"); + entryData.put("Name", "Company2"); + entryData.put("Location", null); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", "3"); + entryData.put("NGO", false); + Map<String, Object> locationData = new HashMap<String, Object>(); + Map<String, Object> cityData = new HashMap<String, Object>(); + cityData.put("PostalCode", "code3"); + locationData.put("City", cityData); + + entryData.put("Location", locationData); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", "4"); + entryData.put("Kind", "Holding4"); + entryData.put("NGO", null); + Map<String, Object> locationData2 = new HashMap<String, Object>(); + Map<String, Object> cityData2 = new HashMap<String, Object>(); + cityData2.put("PostalCode", "code4"); + cityData2.put("CityName", null); + locationData2.put("City", cityData2); + locationData2.put("Country", null); + + entryData.put("Location", locationData2); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", "5"); + entryData.put("Name", "Company5"); + entryData.put("Kind", "Holding5"); + entryData.put("NGO", true); + Map<String, Object> locationData3 = new HashMap<String, Object>(); + Map<String, Object> cityData3 = new HashMap<String, Object>(); + cityData3.put("PostalCode", "code5"); + cityData3.put("CityName", "city5"); + locationData3.put("City", cityData3); + locationData3.put("Country", "country5"); + + entryData.put("Location", locationData3); + feedData.add(entryData); + + return feedData; + } + + private List<Map<String, Object>> createDataWithInvalidProperty(boolean includeKeys) { + List<Map<String, Object>> feedData = new ArrayList<Map<String, Object>>(); + Map<String, Object> entryData = new HashMap<String, Object>(); + entryData.put("Id", "1"); + entryData.put("Address", "1"); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", "2"); + entryData.put("Name", "Company2"); + entryData.put("Location", null); + feedData.add(entryData); + + return feedData; + } + + private List<Map<String, Object>> createDataWithKeyNull(boolean includeKeys) { + List<Map<String, Object>> feedData = new ArrayList<Map<String, Object>>(); + Map<String, Object> entryData = new HashMap<String, Object>(); + entryData.put("Id", null); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", null); + entryData.put("Name", "Company2"); + entryData.put("Location", null); + feedData.add(entryData); + + return feedData; + } + + private List<Map<String, Object>> createDataWithoutKey(boolean includeKeys) { + List<Map<String, Object>> feedData = new ArrayList<Map<String, Object>>(); + Map<String, Object> entryData = new HashMap<String, Object>(); + entryData.put("Id", "1"); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Name", "Company2"); + entryData.put("Location", null); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Kind", "Holding4"); + entryData.put("NGO", null); + Map<String, Object> locationData2 = new HashMap<String, Object>(); + Map<String, Object> cityData2 = new HashMap<String, Object>(); + cityData2.put("PostalCode", "code4"); + cityData2.put("CityName", null); + locationData2.put("City", cityData2); + locationData2.put("Country", null); + + entryData.put("Location", locationData2); + feedData.add(entryData); + + return feedData; + } } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducerTest.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducerTest.java b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducerTest.java index f667a95..11c52c2 100644 --- a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducerTest.java +++ b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/JsonEntryEntityProducerTest.java @@ -30,7 +30,14 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; import org.apache.olingo.odata2.api.ODataCallback; import org.apache.olingo.odata2.api.edm.Edm; @@ -1374,4 +1381,51 @@ public class JsonEntryEntityProducerTest extends BaseTest { assertNotNull(json); return json; } + + @Test + public void unbalancedPropertyEntryWithInlineEntry() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms"); + Map<String, Object> roomData = new HashMap<String, Object>(); + roomData.put("Id", "1"); + roomData.put("Version", 1); + + ExpandSelectTreeNode node2 = Mockito.mock(ExpandSelectTreeNode.class); + Map<String, ExpandSelectTreeNode> links = new HashMap<String, ExpandSelectTreeNode>(); + links.put("nr_Building", node2); + ExpandSelectTreeNode node1 = Mockito.mock(ExpandSelectTreeNode.class); + Mockito.when(node1.getLinks()).thenReturn(links); + + class EntryCallback implements OnWriteEntryContent { + @Override + public WriteEntryCallbackResult retrieveEntryResult(final WriteEntryCallbackContext context) + throws ODataApplicationException { + Map<String, Object> buildingData = new HashMap<String, Object>(); + buildingData.put("Id", "1"); + buildingData.put("Name", "Building1"); + WriteEntryCallbackResult result = new WriteEntryCallbackResult(); + result.setEntryData(buildingData); + result.setInlineProperties(context.getCurrentWriteProperties()); + return result; + } + } + EntryCallback callback = new EntryCallback(); + Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>(); + callbacks.put("nr_Building", callback); + + final ODataResponse response = + new JsonEntityProvider().writeEntry(entitySet, roomData, + EntityProviderWriteProperties.serviceRoot(URI.create(BASE_URI)).expandSelectTree(node1) + .callbacks(callbacks).isDataBasedPropertySerialization(true).build()); + assertNotNull(response); + assertNotNull(response.getEntity()); + assertNull("EntitypProvider must not set content header", response.getContentHeader()); + + final String json = StringHelper.inputStreamToString((InputStream) response.getEntity()); + assertNotNull(json); + assertEquals("{\"d\":{\"__metadata\":{\"id\":\""+BASE_URI+"Rooms('1')\",\"uri\":\""+BASE_URI+"Rooms('1')\"," + + "\"type\":\"RefScenario.Room\",\"etag\":\"W/\\\"1\\\"\"},\"Id\":\"1\",\"Version\":1," + + "\"nr_Building\":{\"__metadata\":{\"id\":\""+BASE_URI+"Buildings('1')\"," + + "\"uri\":\""+BASE_URI+"Buildings('1')\",\"type\":\"RefScenario.Building\"}," + + "\"Id\":\"1\",\"Name\":\"Building1\"}}}", json); + } } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/JsonFeedEntityProducerTest.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/JsonFeedEntityProducerTest.java b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/JsonFeedEntityProducerTest.java index 43226b6..67d6ec2 100644 --- a/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/JsonFeedEntityProducerTest.java +++ b/odata2-lib/odata-core/src/test/java/org/apache/olingo/odata2/core/ep/producer/JsonFeedEntityProducerTest.java @@ -21,6 +21,9 @@ package org.apache.olingo.odata2.core.ep.producer; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.io.InputStream; import java.net.URI; @@ -29,15 +32,31 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.olingo.odata2.api.ODataCallback; import org.apache.olingo.odata2.api.commons.InlineCount; +import org.apache.olingo.odata2.api.edm.Edm; import org.apache.olingo.odata2.api.edm.EdmEntitySet; +import org.apache.olingo.odata2.api.edm.EdmFacets; +import org.apache.olingo.odata2.api.edm.EdmProperty; +import org.apache.olingo.odata2.api.edm.EdmTyped; +import org.apache.olingo.odata2.api.ep.EntityProviderReadProperties; import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties; +import org.apache.olingo.odata2.api.ep.callback.OnWriteFeedContent; +import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackContext; +import org.apache.olingo.odata2.api.ep.callback.WriteFeedCallbackResult; +import org.apache.olingo.odata2.api.ep.entry.ODataEntry; +import org.apache.olingo.odata2.api.ep.feed.ODataFeed; +import org.apache.olingo.odata2.api.exception.ODataApplicationException; import org.apache.olingo.odata2.api.processor.ODataResponse; +import org.apache.olingo.odata2.api.uri.ExpandSelectTreeNode; +import org.apache.olingo.odata2.core.ep.EntityProviderProducerException; import org.apache.olingo.odata2.core.ep.JsonEntityProvider; +import org.apache.olingo.odata2.core.ep.consumer.JsonEntityConsumer; import org.apache.olingo.odata2.testutil.fit.BaseTest; import org.apache.olingo.odata2.testutil.helper.StringHelper; import org.apache.olingo.odata2.testutil.mock.MockFacade; import org.junit.Test; +import org.mockito.Mockito; /** * @@ -186,4 +205,294 @@ public class JsonFeedEntityProducerTest extends BaseTest { + "\"__next\":\"Rooms?$skiptoken=2\"}}", json); } + + @Test + public void unbalancedPropertyFeed() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> originalData = createData(true); + final ODataResponse response = new JsonEntityProvider().writeFeed(entitySet, originalData, + EntityProviderWriteProperties.serviceRoot(URI.create(BASE_URI)).isDataBasedPropertySerialization(true).build()); + + EntityProviderReadProperties readProperties = EntityProviderReadProperties.init().mergeSemantic(false).build(); + JsonEntityConsumer consumer = new JsonEntityConsumer(); + ODataFeed feed = consumer.readFeed(entitySet, (InputStream) response.getEntity(), readProperties); + + compareList(originalData, feed.getEntries()); + } + + @Test + public void unbalancedPropertyFeedWithInvalidProperty() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> originalData = createDataWithInvalidProperty(true); + final ODataResponse response = new JsonEntityProvider().writeFeed(entitySet, originalData, + EntityProviderWriteProperties.serviceRoot(URI.create(BASE_URI)).isDataBasedPropertySerialization(true).build()); + + EntityProviderReadProperties readProperties = EntityProviderReadProperties.init().mergeSemantic(false).build(); + JsonEntityConsumer consumer = new JsonEntityConsumer(); + ODataFeed feed = consumer.readFeed(entitySet, (InputStream) response.getEntity(), readProperties); + originalData.get(0).remove("Address"); + compareList(originalData, feed.getEntries()); + } + + @Test(expected = EntityProviderProducerException.class) + public void unbalancedPropertyFeedWithNullKey() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> originalData = createDataWithKeyNull(true); + new JsonEntityProvider().writeFeed(entitySet, originalData, + EntityProviderWriteProperties.serviceRoot(URI.create(BASE_URI)).isDataBasedPropertySerialization(true).build()); + } + + @Test(expected = EntityProviderProducerException.class) + public void unbalancedPropertyFeedWithoutKeys() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> originalData = createDataWithoutKey(true); + new JsonEntityProvider().writeFeed(entitySet, originalData, + EntityProviderWriteProperties.serviceRoot(URI.create(BASE_URI)).isDataBasedPropertySerialization(true).build()); + } + + @Test(expected = EntityProviderProducerException.class) + public void unbalancedPropertyFeedWithEmptyData() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> feedData = new ArrayList<Map<String, Object>>(); + Map<String, Object> entryData = new HashMap<String, Object>(); + feedData.add(entryData); + new JsonEntityProvider().writeFeed(entitySet, feedData, + EntityProviderWriteProperties.serviceRoot(URI.create(BASE_URI)).isDataBasedPropertySerialization(true).build()); + } + + @Test + public void unbalancedPropertyFeedWithSelect() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Companys"); + List<Map<String, Object>> originalData = createData(true); + List<String> selectedPropertyNames = new ArrayList<String>(); + selectedPropertyNames.add("Id"); + selectedPropertyNames.add("Location"); + ExpandSelectTreeNode select = + ExpandSelectTreeNode.entitySet(entitySet).selectedProperties(selectedPropertyNames).build(); + + final ODataResponse response = new JsonEntityProvider().writeFeed(entitySet, originalData, + EntityProviderWriteProperties.serviceRoot(URI.create(BASE_URI)).expandSelectTree(select). + isDataBasedPropertySerialization(true).build()); + + EntityProviderReadProperties readProperties = EntityProviderReadProperties.init().mergeSemantic(false).build(); + JsonEntityConsumer consumer = new JsonEntityConsumer(); + ODataFeed feed = consumer.readFeed(entitySet, (InputStream) response.getEntity(), readProperties); + + compareList(originalData, feed.getEntries()); + } + + private void compareList(List<Map<String, Object>> expectedList, List<ODataEntry> actualList) { + assertEquals(expectedList.size(), actualList.size()); + + for (int i = 0; i < expectedList.size(); i++) { + Map<String, Object> expected = expectedList.get(i); + Map<String, Object> actual = actualList.get(i).getProperties(); + compareMap(i, expected, actual); + } + } + + @SuppressWarnings("unchecked") + private void compareMap(int index, Map<String, Object> expected, Map<String, Object> actual) { + + assertEquals("Entry: " + index + " does not contain the same amount of properties", expected.size(), + actual.size()); + for (Map.Entry<String, Object> entry : expected.entrySet()) { + String key = entry.getKey(); + assertTrue("Entry " + index + " should contain key: " + key, actual.containsKey(key)); + + if (entry.getValue() instanceof Map<?, ?>) { + assertTrue("Entry " + index + " Value: " + key + " should be a map", actual.get(key) instanceof Map<?, ?>); + compareMap(index, (Map<String, Object>) entry.getValue(), (Map<String, Object>) actual.get(key)); + } else { + if ("Location".equals(key) || "City".equals(key)) { + assertTrue("Entry " + index + " null complex value should result in map", + actual.get(key) instanceof Map<?, ?>); + assertEquals("Entry " + index + " null complex value should result in empty map", + 0, ((Map<String, Object>) actual.get(key)).size()); + } else { + assertEquals("Entry: " + index + " values are not the same: " + key, entry.getValue(), actual.get(key)); + } + } + } + } + + private List<Map<String, Object>> createData(boolean includeKeys) { + List<Map<String, Object>> feedData = new ArrayList<Map<String, Object>>(); + Map<String, Object> entryData = new HashMap<String, Object>(); + entryData.put("Id", "1"); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", "2"); + entryData.put("Name", "Company2"); + entryData.put("Location", null); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", "3"); + entryData.put("NGO", false); + Map<String, Object> locationData = new HashMap<String, Object>(); + Map<String, Object> cityData = new HashMap<String, Object>(); + cityData.put("PostalCode", "code3"); + locationData.put("City", cityData); + + entryData.put("Location", locationData); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", "4"); + entryData.put("Kind", "Holding4"); + entryData.put("NGO", null); + Map<String, Object> locationData2 = new HashMap<String, Object>(); + Map<String, Object> cityData2 = new HashMap<String, Object>(); + cityData2.put("PostalCode", "code4"); + cityData2.put("CityName", null); + locationData2.put("City", cityData2); + locationData2.put("Country", null); + + entryData.put("Location", locationData2); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", "5"); + entryData.put("Name", "Company5"); + entryData.put("Kind", "Holding5"); + entryData.put("NGO", true); + Map<String, Object> locationData3 = new HashMap<String, Object>(); + Map<String, Object> cityData3 = new HashMap<String, Object>(); + cityData3.put("PostalCode", "code5"); + cityData3.put("CityName", "city5"); + locationData3.put("City", cityData3); + locationData3.put("Country", "country5"); + + entryData.put("Location", locationData3); + feedData.add(entryData); + + return feedData; + } + + private List<Map<String, Object>> createDataWithKeyNull(boolean includeKeys) { + List<Map<String, Object>> feedData = new ArrayList<Map<String, Object>>(); + Map<String, Object> entryData = new HashMap<String, Object>(); + entryData.put("Id", null); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", null); + entryData.put("Name", "Company2"); + entryData.put("Location", null); + feedData.add(entryData); + + return feedData; + } + + private List<Map<String, Object>> createDataWithoutKey(boolean includeKeys) { + List<Map<String, Object>> feedData = new ArrayList<Map<String, Object>>(); + Map<String, Object> entryData = new HashMap<String, Object>(); + entryData.put("Id", "1"); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Name", "Company2"); + entryData.put("Location", null); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Kind", "Holding4"); + entryData.put("NGO", null); + Map<String, Object> locationData2 = new HashMap<String, Object>(); + Map<String, Object> cityData2 = new HashMap<String, Object>(); + cityData2.put("PostalCode", "code4"); + cityData2.put("CityName", null); + locationData2.put("City", cityData2); + locationData2.put("Country", null); + + entryData.put("Location", locationData2); + feedData.add(entryData); + + return feedData; + } + + private List<Map<String, Object>> createDataWithInvalidProperty(boolean includeKeys) { + List<Map<String, Object>> feedData = new ArrayList<Map<String, Object>>(); + Map<String, Object> entryData = new HashMap<String, Object>(); + entryData.put("Id", "1"); + entryData.put("Address", "1"); + feedData.add(entryData); + + entryData = new HashMap<String, Object>(); + entryData.put("Id", "2"); + entryData.put("Name", "Company2"); + entryData.put("Location", null); + feedData.add(entryData); + + return feedData; + } + + @Test + public void unbalancedPropertyEntryWithInlineFeed() throws Exception { + Edm edm = MockFacade.getMockEdm(); + EdmTyped imageUrlProperty = edm.getEntityType("RefScenario", "Employee").getProperty("ImageUrl"); + EdmFacets facets = mock(EdmFacets.class); + when(facets.getMaxLength()).thenReturn(1); + when(((EdmProperty) imageUrlProperty).getFacets()).thenReturn(facets); + + Map<String, Object> roomData = new HashMap<String, Object>(); + roomData.put("Id", "1"); + roomData.put("Name", "Neu Schwanstein"); + roomData.put("Seats", new Integer(20)); + + ExpandSelectTreeNode node2 = Mockito.mock(ExpandSelectTreeNode.class); + Map<String, ExpandSelectTreeNode> links = new HashMap<String, ExpandSelectTreeNode>(); + links.put("nr_Employees", node2); + ExpandSelectTreeNode node1 = Mockito.mock(ExpandSelectTreeNode.class); + Mockito.when(node1.getLinks()).thenReturn(links); + + class EntryCallback implements OnWriteFeedContent { + @Override + public WriteFeedCallbackResult retrieveFeedResult(final WriteFeedCallbackContext context) + throws ODataApplicationException { + List<Map<String, Object>> listData = new ArrayList<Map<String, Object>>(); + Map<String, Object> data = new HashMap<String, Object>(); + data.put("EmployeeId", "1"); + data.put("EmployeeName", "EmpName1"); + data.put("RoomId", "1"); + listData.add(data); + + data = new HashMap<String, Object>(); + data.put("EmployeeId", "1"); + data.put("RoomId", "1"); + listData.add(data); + WriteFeedCallbackResult result = new WriteFeedCallbackResult(); + result.setFeedData(listData); + result.setInlineProperties(context.getCurrentWriteProperties()); + return result; + } + } + + EntryCallback callback = new EntryCallback(); + Map<String, ODataCallback> callbacks = new HashMap<String, ODataCallback>(); + callbacks.put("nr_Employees", callback); + + EdmEntitySet entitySet = edm.getDefaultEntityContainer().getEntitySet("Rooms"); + final ODataResponse response = + new JsonEntityProvider().writeEntry(entitySet, roomData, + EntityProviderWriteProperties.serviceRoot(URI.create(BASE_URI)).expandSelectTree(node1) + .callbacks(callbacks).isDataBasedPropertySerialization(true).build()); + assertNotNull(response); + assertNotNull(response.getEntity()); + + final String json = StringHelper.inputStreamToString((InputStream) response.getEntity()); + assertNotNull(json); + assertEquals("{\"d\":{\"__metadata\":{\"id\":\""+BASE_URI+"Rooms('1')\",\"uri\":\""+BASE_URI+"Rooms('1')\"," + + "\"type\":\"RefScenario.Room\"},\"Id\":\"1\",\"Name\":\"Neu Schwanstein\",\"Seats\":20,\"nr_Employees\":" + + "{\"results\":[{\"__metadata\":{\"id\":\""+BASE_URI+"Employees('1')\",\"uri\":\""+BASE_URI+"Employees('1')\"," + + "\"type\":\"RefScenario.Employee\",\"content_type\":\"application/octet-stream\",\"media_src\":\"" + + BASE_URI+"Employees('1')/$value\",\"edit_media\":\""+BASE_URI+"Employees('1')/$value\"},\"EmployeeId\":\"1\"," + + "\"EmployeeName\":\"EmpName1\",\"RoomId\":\"1\"},{\"__metadata\":{\"id\":\""+BASE_URI+"Employees('1')\"," + + "\"uri\":\""+BASE_URI+"Employees('1')\"," + + "\"type\":\"RefScenario.Employee\",\"content_type\":\"application/octet-stream\",\"media_src\":\"" + +BASE_URI+"Employees('1')/$value\",\"edit_media\":\""+BASE_URI+"Employees('1')/$value\"},\"EmployeeId\":\"1\"," + + "\"RoomId\":\"1\"}]}}}", json); + } } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/9142cbd3/odata2-lib/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/EdmMock.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/EdmMock.java b/odata2-lib/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/EdmMock.java index 94aa37a..e291d22 100644 --- a/odata2-lib/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/EdmMock.java +++ b/odata2-lib/odata-testutil/src/main/java/org/apache/olingo/odata2/testutil/mock/EdmMock.java @@ -68,6 +68,8 @@ class EdmMock { createEntitySetMock(defaultContainer, "Managers", EdmSimpleTypeKind.String, "EmployeeId"); final EdmEntitySet buildingEntitySet = createEntitySetMock(defaultContainer, "Buildings", EdmSimpleTypeKind.String, "Id"); + final EdmEntitySet companiesEntitySet = + createEntitySetMock(defaultContainer, "Companys", EdmSimpleTypeKind.String, "Id"); EdmEntityType employeeType = employeeEntitySet.getEntityType(); when(employeeType.hasStream()).thenReturn(true); @@ -168,6 +170,13 @@ class EdmMock { when(buildingType.getNavigationPropertyNames()).thenReturn(Arrays.asList("nb_Rooms")); createNavigationProperty("nb_Rooms", EdmMultiplicity.MANY, buildingEntitySet, roomEntitySet); + EdmEntityType companyType = companiesEntitySet.getEntityType(); + when(companyType.getPropertyNames()).thenReturn(Arrays.asList("Id", "Name", "Kind", "NGO", "Location")); + when(companyType.getProperty("Location")).thenReturn(locationComplexProperty); + createProperty("Name", EdmSimpleTypeKind.String, companyType); + createProperty("Kind", EdmSimpleTypeKind.String, companyType); + createProperty("NGO", EdmSimpleTypeKind.Boolean, companyType); + EdmFunctionImport employeeSearchFunctionImport = createFunctionImportMock(defaultContainer, "EmployeeSearch", employeeType, EdmMultiplicity.MANY); when(employeeSearchFunctionImport.getEntitySet()).thenReturn(employeeEntitySet); @@ -283,6 +292,7 @@ class EdmMock { when(edm.getEntityType("RefScenario", "Building")).thenReturn(buildingType); when(edm.getComplexType("RefScenario", "c_Location")).thenReturn(locationComplexType); when(edm.getEntityType("RefScenario2", "Photo")).thenReturn(photoEntityType); + when(edm.getEntityType("RefScenario", "Company")).thenReturn(companyType); return edm; }
