diff --git a/kontent-delivery/src/main/java/kentico/kontent/delivery/StronglyTypedContentItemConverter.java b/kontent-delivery/src/main/java/kentico/kontent/delivery/StronglyTypedContentItemConverter.java index 84c0f6bc..96944f8a 100644 --- a/kontent-delivery/src/main/java/kentico/kontent/delivery/StronglyTypedContentItemConverter.java +++ b/kontent-delivery/src/main/java/kentico/kontent/delivery/StronglyTypedContentItemConverter.java @@ -193,6 +193,8 @@ private Object getValueForField( } //Check to see if this is an explicitly mapped ContentItem ContentItemMapping contentItemMapping = field.getAnnotation(ContentItemMapping.class); + + // Modular content mapping - linked items element if (contentItemMapping != null && isListOrMap(field.getType()) && item.getElements().containsKey(contentItemMapping.value()) && @@ -203,8 +205,9 @@ private Object getValueForField( for (String codename : linkedItemElement.getValue()) { referencedLinkedItems.put(codename, linkedItems.get(codename)); } - return getCastedLinkedItemsForListOrMap(bean, field, referencedLinkedItems); + return getCastedLinkedItemsForListOrMap(bean, field, referencedLinkedItems, linkedItems); } + // Single Linked Item mapping if (contentItemMapping != null && linkedItems.containsKey(contentItemMapping.value())) { return getCastedLinkedItemsForField(field.getType(), contentItemMapping.value(), linkedItems); } @@ -222,7 +225,7 @@ private Object getValueForField( //Check to see if this is a collection of implicitly mapped ContentItem if (isListOrMap(field.getType())) { - return getCastedLinkedItemsForListOrMap(bean, field, linkedItems); + return getCastedLinkedItemsForListOrMap(bean, field, linkedItems, linkedItems); } return null; } @@ -239,7 +242,7 @@ private Object getCastedLinkedItemsForField( } private Object getCastedLinkedItemsForListOrMap( - Object bean, Field field, Map linkedItems) { + Object bean, Field field, Map referencedLinkedItems, Map allLinkedItems) { Type type = getType(bean, field); if (type == null) { // We have failed to get the type, probably due to a missing setter, skip this field @@ -247,7 +250,7 @@ private Object getCastedLinkedItemsForListOrMap( return null; } if (type == ContentItem.class) { - return castCollection(field.getType(), linkedItems); + return castCollection(field.getType(), referencedLinkedItems); } Class listClass = (Class) type; String contentType = null; @@ -259,11 +262,12 @@ private Object getCastedLinkedItemsForListOrMap( contentType = classNameToContentTypeMapping.get(listClass.getName()); } if (contentType != null) { - HashMap convertedLinkedItems = new HashMap<>(); - for (Map.Entry entry : linkedItems.entrySet()) { + // This type preserves the order of the insertion, to have the same order as in delivery response + LinkedHashMap convertedLinkedItems = new LinkedHashMap(); + for (Map.Entry entry : referencedLinkedItems.entrySet()) { if (entry.getValue() != null && contentType.equals(entry.getValue().getSystem().getType())) { Map linkedItemsForRecursion = - copyLinkedItemsWithExclusion(linkedItems, entry.getKey()); + copyLinkedItemsWithExclusion(allLinkedItems, entry.getKey()); convertedLinkedItems.put(entry.getKey(), convert(entry.getValue(), linkedItemsForRecursion, listClass)); } diff --git a/kontent-delivery/src/test/java/kentico/kontent/delivery/DeliveryClientTest.java b/kontent-delivery/src/test/java/kentico/kontent/delivery/DeliveryClientTest.java index 8cb54808..652f90a0 100644 --- a/kontent-delivery/src/test/java/kentico/kontent/delivery/DeliveryClientTest.java +++ b/kontent-delivery/src/test/java/kentico/kontent/delivery/DeliveryClientTest.java @@ -91,8 +91,8 @@ public void testCustomHeadersPropagatedWithSdkIdHeader() throws Exception { String projectId = "02a70003-e864-464e-b62c-e0ede97deb8c"; String previewApiKey = "preview_api_key"; List
headers = Arrays.asList( - new Header("test-header-name1", "test-header-value1"), - new Header("test-header-name2", "test-header-value2") + new Header("test-header-name1", "test-header-value1"), + new Header("test-header-name2", "test-header-value2") ); this.serverBootstrap.registerHandler( @@ -150,7 +150,7 @@ public void testCustomHeadersDoNotOverwriteReservedHeaders() throws Exception { Assert.assertNotEquals(headers.get(0).getValue(), request.getHeaders(headers.get(0).getName())[0].getValue()); Assert.assertNotEquals(headers.get(1).getValue(), request.getHeaders(headers.get(1).getName())[0].getValue()); Assert.assertNotEquals(headers.get(2).getValue(), request.getHeaders(headers.get(2).getName())[0].getValue()); - Assert.assertArrayEquals(new Header[] {}, request.getHeaders(headers.get(3).getName())); + Assert.assertArrayEquals(new Header[]{}, request.getHeaders(headers.get(3).getName())); response.setEntity( new InputStreamEntity( @@ -837,6 +837,47 @@ public void testGetStronglyTypedItem() throws Exception { Assert.assertEquals(2, item.getAllLinkedItemsMap().size()); } + @Test + public void testGetStronglyTypedNestedItems() throws Exception { + String projectId = "bac6b90c-4f0d-01e9-a3d8-3bc0ec36c3e3"; + + this.serverBootstrap.registerHandler( + String.format("/%s/%s", projectId, "items/sample_page"), + (request, response, context) -> response.setEntity( + new InputStreamEntity( + this.getClass().getResourceAsStream("SampleNestedItemWithDepth2.json") + ) + )); + HttpHost httpHost = this.start(); + DeliveryClient client = new DeliveryClient(projectId); + + String testServerUri = httpHost.toURI(); + client.getDeliveryOptions().setProductionEndpoint(testServerUri); + + kentico.kontent.delivery.nestedmodels.Page item = client.getItem( + "sample_page", + kentico.kontent.delivery.nestedmodels.Page.class, + DeliveryParameterBuilder.params() + .linkedItemsDepth(2) + .build() + ).toCompletableFuture() + .get(); + + Assert.assertNotNull(item); + Assert.assertEquals("Sample page",item.getTitle()); + Assert.assertEquals(2, item.getSections().size()); + + Assert.assertEquals("Section 1", item.getSections().get(0).getHeadline()); + Assert.assertEquals(2, item.getSections().get(0).getSectionParts().size()); + Assert.assertEquals("Part 1", item.getSections().get(0).getSectionParts().get(0).getTitle()); + Assert.assertEquals("Part 2", item.getSections().get(0).getSectionParts().get(1).getTitle()); + + Assert.assertEquals("Section 2", item.getSections().get(1).getHeadline()); + Assert.assertEquals(2, item.getSections().get(1).getSectionParts().size()); + Assert.assertEquals("Part A", item.getSections().get(1).getSectionParts().get(0).getTitle()); + Assert.assertEquals("Part B", item.getSections().get(1).getSectionParts().get(1).getTitle()); + } + @Test public void securedAPI() { diff --git a/kontent-delivery/src/test/java/kentico/kontent/delivery/nestedmodels/Page.java b/kontent-delivery/src/test/java/kentico/kontent/delivery/nestedmodels/Page.java new file mode 100644 index 00000000..14d1984a --- /dev/null +++ b/kontent-delivery/src/test/java/kentico/kontent/delivery/nestedmodels/Page.java @@ -0,0 +1,43 @@ +package kentico.kontent.delivery.nestedmodels; + +import kentico.kontent.delivery.ContentItemMapping; +import kentico.kontent.delivery.ElementMapping; +import kentico.kontent.delivery.System; + +import java.util.List; + +@ContentItemMapping("page") +public class Page { + @ElementMapping("title") + String title; + + // Custom strongly type mapping to List + @ContentItemMapping("sections") + List
sections; + + System system; + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public List
getSections() { + return sections; + } + + public void setSections(List
sections) { + this.sections = sections; + } + + public System getSystem() { + return system; + } + + public void setSystem(System system) { + this.system = system; + } +} diff --git a/kontent-delivery/src/test/java/kentico/kontent/delivery/nestedmodels/Section.java b/kontent-delivery/src/test/java/kentico/kontent/delivery/nestedmodels/Section.java new file mode 100644 index 00000000..548e1f65 --- /dev/null +++ b/kontent-delivery/src/test/java/kentico/kontent/delivery/nestedmodels/Section.java @@ -0,0 +1,44 @@ +package kentico.kontent.delivery.nestedmodels; + +import kentico.kontent.delivery.ContentItemMapping; +import kentico.kontent.delivery.ElementMapping; +import kentico.kontent.delivery.System; + +import java.util.List; + +@ContentItemMapping("section") +public class Section { + + // Custom strongly type mapping to List + @ContentItemMapping("section_parts") + List sectionParts; + + @ElementMapping("headline") + String headline; + + System system; + + public List getSectionParts() { + return sectionParts; + } + + public void setSectionParts(List sectionParts) { + this.sectionParts = sectionParts; + } + + public String getHeadline() { + return headline; + } + + public void setHeadline(String headline) { + this.headline = headline; + } + + public System getSystem() { + return system; + } + + public void setSystem(System system) { + this.system = system; + } +} diff --git a/kontent-delivery/src/test/java/kentico/kontent/delivery/nestedmodels/SectionPart.java b/kontent-delivery/src/test/java/kentico/kontent/delivery/nestedmodels/SectionPart.java new file mode 100644 index 00000000..1c28bfc5 --- /dev/null +++ b/kontent-delivery/src/test/java/kentico/kontent/delivery/nestedmodels/SectionPart.java @@ -0,0 +1,42 @@ +package kentico.kontent.delivery.nestedmodels; + +import kentico.kontent.delivery.ContentItemMapping; +import kentico.kontent.delivery.ElementMapping; +import kentico.kontent.delivery.System; + +@ContentItemMapping("section_part") +public class SectionPart { + + // Custom strongly type mapping to List + @ElementMapping("content") + String content; + + @ElementMapping("title") + String title; + + System system; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public System getSystem() { + return system; + } + + public void setSystem(System system) { + this.system = system; + } +} diff --git a/kontent-delivery/src/test/resources/kentico/kontent/delivery/SampleNestedItemWithDepth2.json b/kontent-delivery/src/test/resources/kentico/kontent/delivery/SampleNestedItemWithDepth2.json new file mode 100644 index 00000000..d39b23f3 --- /dev/null +++ b/kontent-delivery/src/test/resources/kentico/kontent/delivery/SampleNestedItemWithDepth2.json @@ -0,0 +1,193 @@ +{ + "item": { + "system": { + "id": "78a6da18-f471-4985-9481-5aa4d5742eaf", + "name": "Sample page", + "codename": "sample_page", + "language": "default", + "type": "page", + "collection": "default", + "sitemap_locations": [], + "last_modified": "2021-02-02T09:10:51.3307835Z" + }, + "elements": { + "title": { + "type": "text", + "name": "Title", + "value": "Sample page" + }, + "sections": { + "type": "modular_content", + "name": "Sections", + "value": [ + "section_1", + "section_2" + ] + } + } + }, + "modular_content": { + "part_1": { + "system": { + "id": "d540def8-c80b-4a2c-a7a2-29c6d2f7da7f", + "name": "Part 1", + "codename": "part_1", + "language": "default", + "type": "section_part", + "collection": "default", + "sitemap_locations": [], + "last_modified": "2021-02-02T09:10:11.2409499Z" + }, + "elements": { + "title": { + "type": "text", + "name": "Title", + "value": "Part 1" + }, + "content": { + "type": "rich_text", + "name": "Content", + "images": {}, + "links": {}, + "modular_content": [], + "value": "

Fusce vulputate eleifend sapien. Maecenas ullamcorper, dui et placerat feugiat, eros pede varius nisi, condimentum viverra felis nunc et lorem. Ut varius tincidunt libero. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Cras non dolor.

" + } + } + }, + "part_2": { + "system": { + "id": "41d38e04-f59c-4b11-a99f-6c229cecc52f", + "name": "Part 2", + "codename": "part_2", + "language": "default", + "type": "section_part", + "collection": "default", + "sitemap_locations": [], + "last_modified": "2021-02-02T09:10:36.5025251Z" + }, + "elements": { + "title": { + "type": "text", + "name": "Title", + "value": "Part 2" + }, + "content": { + "type": "rich_text", + "name": "Content", + "images": {}, + "links": {}, + "modular_content": [], + "value": "

Nunc interdum lacus sit amet orci. Aenean posuere, tortor sed cursus feugiat, nunc augue blandit nunc, eu sollicitudin urna dolor sagittis lacus. Pellentesque auctor neque nec urna. In ac felis quis tortor malesuada pretium. Integer tincidunt.

" + } + } + }, + "part_a": { + "system": { + "id": "d7c8b378-8cac-44f1-9b05-220bbb089a42", + "name": "Part A", + "codename": "part_a", + "language": "default", + "type": "section_part", + "collection": "default", + "sitemap_locations": [], + "last_modified": "2021-02-02T09:11:12.0549009Z" + }, + "elements": { + "title": { + "type": "text", + "name": "Title", + "value": "Part A" + }, + "content": { + "type": "rich_text", + "name": "Content", + "images": {}, + "links": {}, + "modular_content": [], + "value": "

Proin sapien ipsum, porta a, auctor quis, euismod ut, mi. Fusce vel dui. Vestibulum rutrum, mi nec elementum vehicula, eros quam gravida nisl, id fringilla neque ante vel mi. Etiam rhoncus. Phasellus blandit leo ut odio.

" + } + } + }, + "part_b": { + "system": { + "id": "8156372b-9dde-46b1-b285-31d66c440b08", + "name": "Part B", + "codename": "part_b", + "language": "default", + "type": "section_part", + "collection": "default", + "sitemap_locations": [], + "last_modified": "2021-02-02T09:11:27.5097744Z" + }, + "elements": { + "title": { + "type": "text", + "name": "Title", + "value": "Part B" + }, + "content": { + "type": "rich_text", + "name": "Content", + "images": {}, + "links": {}, + "modular_content": [], + "value": "

Donec interdum, metus et hendrerit aliquet, dolor diam sagittis ligula, eget egestas libero turpis vel mi. Fusce vel dui. Suspendisse pulvinar, augue ac venenatis condimentum, sem libero volutpat nibh, nec pellentesque velit pede quis nunc. Suspendisse enim turpis, dictum sed, iaculis a, condimentum nec, nisi. Vivamus laoreet.

" + } + } + }, + "section_1": { + "system": { + "id": "01ac5c49-4229-46b2-8aff-b1c0920fdadd", + "name": "Section 1", + "codename": "section_1", + "language": "default", + "type": "section", + "collection": "default", + "sitemap_locations": [], + "last_modified": "2021-02-02T09:10:18.3461414Z" + }, + "elements": { + "headline": { + "type": "text", + "name": "Headline", + "value": "Section 1" + }, + "section_parts": { + "type": "modular_content", + "name": "Section Parts", + "value": [ + "part_1", + "part_2" + ] + } + } + }, + "section_2": { + "system": { + "id": "00a47be9-bbec-49e9-a479-6afeb893a87f", + "name": "Section 2", + "codename": "section_2", + "language": "default", + "type": "section", + "collection": "default", + "sitemap_locations": [], + "last_modified": "2021-02-02T09:11:17.1790928Z" + }, + "elements": { + "headline": { + "type": "text", + "name": "Headline", + "value": "Section 2" + }, + "section_parts": { + "type": "modular_content", + "name": "Section Parts", + "value": [ + "part_a", + "part_b" + ] + } + } + } + } +} \ No newline at end of file