[OLINGO-251] entity writer can serialize additional links Change-Id: I84e4037eaad71baa96c82482c3b438e500b67f5e
Signed-off-by: Stephan Klevenz <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/olingo-odata2/repo Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata2/commit/b9bcff10 Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata2/tree/b9bcff10 Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata2/diff/b9bcff10 Branch: refs/heads/Olingo-129_PocJpaDataStore Commit: b9bcff10ff51c1a3013a38d8e273edfe4af84a56 Parents: 2dd0181 Author: Klaus Straubinger <[email protected]> Authored: Wed Apr 23 09:48:48 2014 +0200 Committer: Stephan Klevenz <[email protected]> Committed: Wed Apr 23 10:32:50 2014 +0200 ---------------------------------------------------------------------- .../api/ep/EntityProviderWriteProperties.java | 27 +++++++++++++-- .../ep/producer/AtomEntryEntityProducer.java | 36 ++++++++++++-------- .../ep/producer/JsonEntryEntityProducer.java | 21 +++++++++--- .../ep/ODataEntityProviderPropertiesTest.java | 9 +++++ .../core/ep/producer/AtomEntryProducerTest.java | 30 ++++++++++++++++ .../producer/JsonEntryEntityProducerTest.java | 24 +++++++++++++ .../odata2/fit/ref/EntryJsonChangeTest.java | 30 +++++++++++++--- 7 files changed, 152 insertions(+), 25 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/b9bcff10/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 98cabb8..93c1082 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 @@ -43,6 +43,7 @@ public class EntityProviderWriteProperties { private Map<String, ODataCallback> callbacks = Collections.emptyMap(); private URI selfLink; private boolean includeSimplePropertyType; + private Map<String, Map<String, Object>> additionalLinks; private EntityProviderWriteProperties() {} @@ -118,6 +119,16 @@ public class EntityProviderWriteProperties { return nextLink; } + /** + * Gets the additional links that should be in the payload. + * @return the additional links as Map where the navigation-property name is the key and + * a key predicate is the value - + * a key predicate is a Map from key-property names to their values + */ + public final Map<String, Map<String, Object>> getAdditionalLinks() { + return additionalLinks; + } + public static ODataEntityProviderPropertiesBuilder serviceRoot(final URI serviceRoot) { return new ODataEntityProviderPropertiesBuilder().serviceRoot(serviceRoot); } @@ -187,8 +198,7 @@ public class EntityProviderWriteProperties { /** * Set a expand select tree which results from $expand and $select query parameter. Usually the data structure is - * constructed - * by the uri parser. + * constructed by the URI parser. * @param expandSelectTree data structure * @return properties builder */ @@ -207,6 +217,18 @@ public class EntityProviderWriteProperties { return this; } + /** + * Sets additional links from this entity to other entities. + * @param links a Map where the navigation-property name is the key and + * a key predicate is the value - + * a key predicate is a Map from key-property names to their values + * @return properties builder + */ + public ODataEntityProviderPropertiesBuilder additionalLinks(final Map<String, Map<String, Object>> links) { + properties.additionalLinks = links; + return this; + } + public ODataEntityProviderPropertiesBuilder fromProperties(final EntityProviderWriteProperties properties) { this.properties.mediaResourceMimeType = properties.getMediaResourceMimeType(); this.properties.inlineCountType = properties.getInlineCountType(); @@ -216,6 +238,7 @@ public class EntityProviderWriteProperties { this.properties.callbacks = properties.getCallbacks(); this.properties.selfLink = properties.getSelfLink(); this.properties.includeSimplePropertyType = properties.includeSimplePropertyType; + this.properties.additionalLinks = properties.additionalLinks; return this; } } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/b9bcff10/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 307c828..95702dd 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 @@ -58,7 +58,6 @@ import org.apache.olingo.odata2.core.commons.Encoder; import org.apache.olingo.odata2.core.edm.EdmDateTimeOffset; import org.apache.olingo.odata2.core.ep.aggregator.EntityInfoAggregator; import org.apache.olingo.odata2.core.ep.aggregator.EntityPropertyInfo; -import org.apache.olingo.odata2.core.ep.aggregator.NavigationPropertyInfo; import org.apache.olingo.odata2.core.ep.util.FormatXml; /** @@ -182,29 +181,36 @@ public class AtomEntryEntityProducer { private void appendAtomNavigationLinks(final XMLStreamWriter writer, final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException, EdmException, URISyntaxException { for (String name : eia.getSelectedNavigationPropertyNames()) { - NavigationPropertyInfo info = eia.getNavigationPropertyInfo(name); - boolean isFeed = (info.getMultiplicity() == EdmMultiplicity.MANY); - String self = createSelfLink(eia, data, info.getName()); - appendAtomNavigationLink(writer, self, info.getName(), isFeed, eia, data); + final boolean isFeed = (eia.getNavigationPropertyInfo(name).getMultiplicity() == EdmMultiplicity.MANY); + final Map<String, Map<String, Object>> links = properties.getAdditionalLinks(); + final Map<String, Object> key = links == null ? null : links.get(name); + if (key == null || key.isEmpty()) { + appendAtomNavigationLink(writer, createSelfLink(eia, data, name), name, isFeed, eia, data); + } else { + final EntityInfoAggregator targetEntityInfo = EntityInfoAggregator.create( + eia.getEntitySet().getRelatedEntitySet((EdmNavigationProperty) eia.getEntityType().getProperty(name))); + appendAtomNavigationLink(writer, createSelfLink(targetEntityInfo, key, null), name, null, eia, data); + } } } - private void appendAtomNavigationLink(final XMLStreamWriter writer, final String self, - final String navigationPropertyName, final boolean isFeed, final EntityInfoAggregator eia, + private void appendAtomNavigationLink(final XMLStreamWriter writer, final String target, + final String navigationPropertyName, final Boolean isFeed, final EntityInfoAggregator eia, final Map<String, Object> data) throws EntityProviderException, EdmException, URISyntaxException { try { writer.writeStartElement(FormatXml.ATOM_LINK); - writer.writeAttribute(FormatXml.ATOM_HREF, self); + writer.writeAttribute(FormatXml.ATOM_HREF, target); writer.writeAttribute(FormatXml.ATOM_REL, Edm.NAMESPACE_REL_2007_08 + navigationPropertyName); writer.writeAttribute(FormatXml.ATOM_TITLE, navigationPropertyName); - if (isFeed) { - writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_ATOM_XML_FEED.toString()); - appendInlineFeed(writer, navigationPropertyName, eia, data, self); - } else { - writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_ATOM_XML_ENTRY.toString()); - appendInlineEntry(writer, navigationPropertyName, eia, data); + if (isFeed != null) { + if (isFeed) { + writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_ATOM_XML_FEED.toString()); + appendInlineFeed(writer, navigationPropertyName, eia, data, target); + } else { + writer.writeAttribute(FormatXml.ATOM_TYPE, ContentType.APPLICATION_ATOM_XML_ENTRY.toString()); + appendInlineEntry(writer, navigationPropertyName, eia, data); + } } - writer.writeEndElement(); } catch (XMLStreamException e) { throw new EntityProviderException(EntityProviderException.COMMON, e); http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/b9bcff10/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 6ae5677..8ad5d42 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 @@ -110,10 +110,10 @@ public class JsonEntryEntityProducer { if (properties.getCallbacks() != null && properties.getCallbacks().containsKey(navigationPropertyName)) { writeExpandedNavigationProperty(writer, entityInfo, data, type, navigationPropertyName); } else { - writeDeferredUri(navigationPropertyName); + writeDeferredUri(entityInfo, navigationPropertyName); } } else { - writeDeferredUri(navigationPropertyName); + writeDeferredUri(entityInfo, navigationPropertyName); } } } @@ -240,10 +240,23 @@ public class JsonEntryEntityProducer { jsonStreamWriter.endObject(); } - private void writeDeferredUri(final String navigationPropertyName) throws IOException { + private void writeDeferredUri(final EntityInfoAggregator entityInfo, final String navigationPropertyName) + throws IOException, EntityProviderException, EdmException { jsonStreamWriter.beginObject() .name(FormatJson.DEFERRED); - JsonLinkEntityProducer.appendUri(jsonStreamWriter, location + "/" + Encoder.encode(navigationPropertyName)); + String target = null; + final Map<String, Map<String, Object>> links = properties.getAdditionalLinks(); + final Map<String, Object> key = links == null ? null : links.get(navigationPropertyName); + if (key == null || key.isEmpty()) { + target = location + "/" + Encoder.encode(navigationPropertyName); + } else { + final EntityInfoAggregator targetEntityInfo = EntityInfoAggregator.create( + entityInfo.getEntitySet().getRelatedEntitySet( + (EdmNavigationProperty) entityInfo.getEntityType().getProperty(navigationPropertyName))); + target = (properties.getServiceRoot() == null ? "" : properties.getServiceRoot().toASCIIString()) + + AtomEntryEntityProducer.createSelfLink(targetEntityInfo, key, null); + } + JsonLinkEntityProducer.appendUri(jsonStreamWriter, target); jsonStreamWriter.endObject(); } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/b9bcff10/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 e0e85f6..b5d178e 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 @@ -22,6 +22,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.net.URI; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -61,6 +62,8 @@ public class ODataEntityProviderPropertiesTest extends BaseTest { callbacks.put("aCallback", new MyCallback(null, null)); ExpandSelectTreeNode expandSelectTree = new ExpandSelectTreeNodeImpl(); URI selfLink = new URI("http://some.uri"); + Map<String, Map<String, Object>> links = new HashMap<String, Map<String,Object>>(); + links.put("aNavigationProperty", Collections.<String, Object> emptyMap()); final EntityProviderWriteProperties properties = EntityProviderWriteProperties.serviceRoot(serviceRoot) .callbacks(callbacks) .expandSelectTree(expandSelectTree) @@ -70,6 +73,7 @@ public class ODataEntityProviderPropertiesTest extends BaseTest { .nextLink("http://localhost") .selfLink(selfLink) .includeSimplePropertyType(true) + .additionalLinks(links) .build(); assertEquals("Wrong amount of callbacks.", 1, properties.getCallbacks().size()); @@ -82,6 +86,7 @@ public class ODataEntityProviderPropertiesTest extends BaseTest { assertEquals("Wrong inline count.", Integer.valueOf(1), properties.getInlineCount()); assertEquals("Wrong nextLink", "http://localhost", properties.getNextLink()); assertTrue("Simple property types should be true", properties.isIncludeSimplePropertyType()); + assertEquals(Collections.emptyMap(), properties.getAdditionalLinks().get("aNavigationProperty")); } @Test @@ -101,6 +106,8 @@ public class ODataEntityProviderPropertiesTest extends BaseTest { callbacks.put("aCallback", new MyCallback(null, null)); ExpandSelectTreeNode expandSelectTree = new ExpandSelectTreeNodeImpl(); URI selfLink = new URI("http://some.uri"); + Map<String, Map<String, Object>> links = new HashMap<String, Map<String,Object>>(); + links.put("aNavigationProperty", Collections.<String, Object> emptyMap()); final EntityProviderWriteProperties properties = EntityProviderWriteProperties.serviceRoot(serviceRoot) .callbacks(callbacks) .expandSelectTree(expandSelectTree) @@ -110,6 +117,7 @@ public class ODataEntityProviderPropertiesTest extends BaseTest { .nextLink("http://localhost") .selfLink(selfLink) .includeSimplePropertyType(true) + .additionalLinks(links) .build(); // @@ -127,5 +135,6 @@ public class ODataEntityProviderPropertiesTest extends BaseTest { assertEquals("Wrong inline count.", Integer.valueOf(1), fromProperties.getInlineCount()); assertEquals("Wrong nextLink", "http://localhost", fromProperties.getNextLink()); assertTrue("Simple property types should be true", fromProperties.isIncludeSimplePropertyType()); + assertEquals(Collections.emptyMap(), fromProperties.getAdditionalLinks().get("aNavigationProperty")); } } http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/b9bcff10/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 dd36e11..c4bcb22 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 @@ -720,6 +720,36 @@ public class AtomEntryProducerTest extends AbstractProviderTest { } @Test + public void additionalLink() throws Exception { + Map<String, Map<String, Object>> links = new HashMap<String, Map<String, Object>>(); + links.put("nr_Building", buildingData); + final ODataResponse response = createAtomEntityProvider().writeEntry( + MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms"), roomData, + EntityProviderWriteProperties.serviceRoot(BASE_URI).additionalLinks(links).build()); + final String xmlString = verifyResponse(response); + + assertXpathExists("/a:entry/a:link[@title='nr_Building']", xmlString); + assertXpathNotExists("/a:entry/a:link[@href=\"Rooms('1')/nr_Building\"]", xmlString); + assertXpathExists("/a:entry/a:link[@href=\"Buildings('1')\"]", xmlString); + assertXpathNotExists("/a:entry/a:link[@type='application/atom+xml;type=entry']", xmlString); + } + + @Test + public void additionalLinkToOneOfMany() throws Exception { + Map<String, Map<String, Object>> links = new HashMap<String, Map<String, Object>>(); + links.put("nr_Employees", employeeData); + final ODataResponse response = createAtomEntityProvider().writeEntry( + MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms"), roomData, + EntityProviderWriteProperties.serviceRoot(BASE_URI).additionalLinks(links).build()); + final String xmlString = verifyResponse(response); + + assertXpathExists("/a:entry/a:link[@title='nr_Employees']", xmlString); + assertXpathNotExists("/a:entry/a:link[@href=\"Rooms('1')/nr_Employees\"]", xmlString); + assertXpathExists("/a:entry/a:link[@href=\"Employees('1')\"]", xmlString); + assertXpathNotExists("/a:entry/a:link[@type='application/atom+xml;type=feed']", xmlString); + } + + @Test public void serializeWithCustomSrcAttributeOnEmployee() throws Exception { AtomEntityProvider ser = createAtomEntityProvider(); Map<String, Object> localEmployeeData = new HashMap<String, Object>(employeeData); http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/b9bcff10/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 0a6f08a..8efd99a 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 @@ -737,6 +737,30 @@ public class JsonEntryEntityProducerTest extends BaseTest { assertEquals("right", jsonMap.get("content_type")); } + @Test + public void additionalLink() throws Exception { + final EdmEntitySet entitySet = MockFacade.getMockEdm().getDefaultEntityContainer().getEntitySet("Rooms"); + Map<String, Object> data = new HashMap<String, Object>(); + data.put("Id", "1"); + Map<String, Object> key = new HashMap<String, Object>(); + key.put("Id", "3"); + Map<String, Map<String, Object>> links = new HashMap<String, Map<String, Object>>(); + links.put("nr_Building", key); + final EntityProviderWriteProperties properties = EntityProviderWriteProperties + .serviceRoot(URI.create(BASE_URI)) + .additionalLinks(links) + .build(); + + final ODataResponse response = new JsonEntityProvider().writeEntry(entitySet, data, properties); + final String json = verifyResponse(response); + assertEquals("{\"d\":{\"__metadata\":{\"id\":\"" + BASE_URI + "Rooms('1')\"," + + "\"uri\":\"" + BASE_URI + "Rooms('1')\",\"type\":\"RefScenario.Room\"}," + + "\"Id\":\"1\",\"Name\":null,\"Seats\":null,\"Version\":null," + + "\"nr_Employees\":{\"__deferred\":{\"uri\":\"" + BASE_URI + "Rooms('1')/nr_Employees\"}}," + + "\"nr_Building\":{\"__deferred\":{\"uri\":\"" + BASE_URI + "Buildings('3')\"}}}}", + json); + } + private String verifyResponse(final ODataResponse response) throws IOException { assertNotNull(response); assertNotNull(response.getEntity()); http://git-wip-us.apache.org/repos/asf/olingo-odata2/blob/b9bcff10/odata2-lib/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonChangeTest.java ---------------------------------------------------------------------- diff --git a/odata2-lib/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonChangeTest.java b/odata2-lib/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonChangeTest.java index 8fd04ce..9c1df43 100644 --- a/odata2-lib/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonChangeTest.java +++ b/odata2-lib/odata-fit/src/test/java/org/apache/olingo/odata2/fit/ref/EntryJsonChangeTest.java @@ -21,10 +21,18 @@ package org.apache.olingo.odata2.fit.ref; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + import org.apache.http.HttpResponse; import org.apache.olingo.odata2.api.commons.HttpContentType; import org.apache.olingo.odata2.api.commons.HttpStatusCodes; import org.apache.olingo.odata2.api.commons.ODataHttpMethod; +import org.apache.olingo.odata2.api.edm.EdmEntitySet; +import org.apache.olingo.odata2.api.ep.EntityProvider; +import org.apache.olingo.odata2.api.ep.EntityProviderWriteProperties; +import org.apache.olingo.odata2.testutil.helper.StringHelper; import org.apache.olingo.odata2.testutil.server.ServletType; import org.junit.Test; @@ -74,10 +82,24 @@ public class EntryJsonChangeTest extends AbstractRefTest { @Test public void createEntryWithLink() throws Exception { - final String requestBody = "{\"Id\":\"99\",\"Name\":\"new room\",\"Seats\":19,\"Version\":42," - + "\"nr_Building\":{\"__deferred\":{\"uri\":\"" + getEndpoint() + "Buildings('1')\"}}}"; - final HttpResponse response = - postUri("Rooms()", requestBody, HttpContentType.APPLICATION_JSON, HttpStatusCodes.CREATED); + HttpResponse response = callUri("$metadata"); + final EdmEntitySet linkedEntitySet = EntityProvider.readMetadata(response.getEntity().getContent(), false) + .getDefaultEntityContainer().getEntitySet("Rooms"); + getBody(response); + Map<String, Object> data = new HashMap<String, Object>(); + data.put("Id", "99"); + data.put("Name", "new room"); + data.put("Seats", 19); + data.put("Version", 42); + Map<String, Object> key = new HashMap<String, Object>(); + key.put("Id", "1"); + Map<String, Map<String, Object>> links = new HashMap<String, Map<String,Object>>(); + links.put("nr_Building", key); + final String requestBody = StringHelper.inputStreamToString( + (InputStream) EntityProvider.writeEntry(HttpContentType.APPLICATION_JSON, linkedEntitySet, data, + EntityProviderWriteProperties.serviceRoot(getEndpoint()).additionalLinks(links).build()) + .getEntity()); + response = postUri("Rooms()", requestBody, HttpContentType.APPLICATION_JSON, HttpStatusCodes.CREATED); assertFalse(getBody(response).isEmpty()); checkUri("Rooms('104')/nr_Building?$format=json"); assertEquals("{\"d\":{\"Name\":\"Building 1\"}}", getBody(callUri("Rooms('104')/nr_Building/Name?$format=json")));
