diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/document/mapper/DocumentMapperUtil.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/document/mapper/DocumentMapperUtil.java index 843922f6a..6e1627330 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/document/mapper/DocumentMapperUtil.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/document/mapper/DocumentMapperUtil.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; + import io.crnk.core.boot.CrnkProperties; import io.crnk.core.engine.document.Resource; import io.crnk.core.engine.document.ResourceIdentifier; @@ -60,10 +61,16 @@ public DocumentMapperUtil(ResourceRegistry resourceRegistry, ObjectMapper object serializerUtil = new SerializerUtil(serializeLinksAsObjects); } - protected static List getRequestedFields(ResourceInformation resourceInformation, QueryAdapter queryAdapter, + protected List getRequestedFields(ResourceInformation resourceInformation, QueryAdapter queryAdapter, List fields, boolean relation) { Map> includedFieldsSet = queryAdapter != null ? queryAdapter.getIncludedFields() : null; - Set includedFields = includedFieldsSet != null ? includedFieldsSet.get(resourceInformation.getResourceType()) : null; + final Set includedFields = new HashSet<>(); + + RegistryEntry entry = resourceRegistry.getEntry(resourceInformation.getResourceType()); + while(entry != null){ + addIfNotNull(includedFields, includedFieldsSet.get(entry.getResourceInformation().getResourceType())); + entry = entry.getParentRegistryEntry(); + } if (noResourceIncludedFieldsSpecified(includedFields)) { return fields; } @@ -72,6 +79,12 @@ protected static List getRequestedFields(ResourceInformation reso } } + private static void addIfNotNull(Set set, Collection collection) { + if (collection != null) { + set.addAll(collection); + } + } + private static List computeRequestedFields(Set includedFields, boolean relation, QueryAdapter queryAdapter, ResourceInformation resourceInformation, List fields) { diff --git a/crnk-core/src/main/java/io/crnk/core/engine/internal/document/mapper/ResourceMapper.java b/crnk-core/src/main/java/io/crnk/core/engine/internal/document/mapper/ResourceMapper.java index 6c581a484..6c9287e4f 100644 --- a/crnk-core/src/main/java/io/crnk/core/engine/internal/document/mapper/ResourceMapper.java +++ b/crnk-core/src/main/java/io/crnk/core/engine/internal/document/mapper/ResourceMapper.java @@ -115,8 +115,7 @@ public LinksInformation getResourceLinks(Object entity, ResourceInformation reso protected void setAttributes(Resource resource, Object entity, ResourceInformation resourceInformation, QueryAdapter queryAdapter, ResourceMappingConfig mappingConfig) { // fields legacy may further limit the number of fields - List fields = DocumentMapperUtil - .getRequestedFields(resourceInformation, queryAdapter, resourceInformation.getAttributeFields(), false); + List fields = util.getRequestedFields(resourceInformation, queryAdapter, resourceInformation.getAttributeFields(), false); // serialize the individual attributes QueryContext queryContext = queryAdapter.getQueryContext(); for (ResourceField field : fields) { @@ -201,8 +200,7 @@ private boolean isValueIncluded(ResourceField field, Object value) { protected void setRelationships(Resource resource, Object entity, ResourceInformation resourceInformation, QueryAdapter queryAdapter, ResourceMappingConfig mappingConfig) { - List fields = DocumentMapperUtil - .getRequestedFields(resourceInformation, queryAdapter, resourceInformation.getRelationshipFields(), true); + List fields = util.getRequestedFields(resourceInformation, queryAdapter, resourceInformation.getRelationshipFields(), true); QueryContext queryContext = queryAdapter.getQueryContext(); for (ResourceField field : fields) { if (!isIgnored(field, queryContext)) { diff --git a/crnk-core/src/test/java/io/crnk/core/CoreTestModule.java b/crnk-core/src/test/java/io/crnk/core/CoreTestModule.java index 67d551310..e097b631e 100644 --- a/crnk-core/src/test/java/io/crnk/core/CoreTestModule.java +++ b/crnk-core/src/test/java/io/crnk/core/CoreTestModule.java @@ -14,6 +14,7 @@ import io.crnk.core.mock.repository.RelationIdTestRepository; import io.crnk.core.mock.repository.RelationshipBehaviorTestRepository; import io.crnk.core.mock.repository.ScheduleRepositoryImpl; +import io.crnk.core.mock.repository.TopTaskRepository; import io.crnk.core.mock.repository.TaskRepository; import io.crnk.core.mock.repository.TaskToProjectRepository; import io.crnk.core.mock.repository.TaskWithLookupRepository; @@ -43,6 +44,7 @@ public void setupModule(ModuleContext context) { context.addRepository(new TaskToProjectRepository()); context.addRepository(new TaskWithLookupRepository()); context.addRepository(new TaskWithLookupToProjectRepository()); + context.addRepository(new TopTaskRepository()); context.addRepository(new UserRepository()); context.addRepository(new UserToProjectRepository()); context.addRepository(new UserToTaskRepository()); diff --git a/crnk-core/src/test/java/io/crnk/core/engine/internal/document/mapper/DocumentMapperTest.java b/crnk-core/src/test/java/io/crnk/core/engine/internal/document/mapper/DocumentMapperTest.java index 0b4b0f408..a1ee51a44 100644 --- a/crnk-core/src/test/java/io/crnk/core/engine/internal/document/mapper/DocumentMapperTest.java +++ b/crnk-core/src/test/java/io/crnk/core/engine/internal/document/mapper/DocumentMapperTest.java @@ -1,6 +1,5 @@ package io.crnk.core.engine.internal.document.mapper; -import java.net.URI; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Arrays; @@ -17,17 +16,20 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.fasterxml.jackson.databind.node.TextNode; + import io.crnk.core.engine.document.Document; import io.crnk.core.engine.document.ErrorData; import io.crnk.core.engine.document.Relationship; import io.crnk.core.engine.document.Resource; import io.crnk.core.engine.document.ResourceIdentifier; import io.crnk.core.engine.query.QueryAdapter; +import io.crnk.core.mock.models.BottomTask; import io.crnk.core.mock.models.LazyTask; +import io.crnk.core.mock.models.MiddleTask; import io.crnk.core.mock.models.Project; import io.crnk.core.mock.models.Schedule; import io.crnk.core.mock.models.Task; +import io.crnk.core.mock.models.TopTask; import io.crnk.core.queryspec.PathSpec; import io.crnk.core.queryspec.QuerySpec; import io.crnk.core.queryspec.internal.QuerySpecAdapter; @@ -43,6 +45,7 @@ import org.junit.Test; import org.mockito.Mockito; + public class DocumentMapperTest extends AbstractDocumentMapperTest { @Test @@ -648,6 +651,41 @@ public void testAttributesSelection() { Assert.assertEquals("sample category", resource.getAttributes().get("category").asText()); } + @Test + public void testSupertypeAttributesSelection() { + BottomTask task = createTreeTask(2, "sample task"); + task.setEnd("next month"); + task.setRecurring(true); + task.setPublicComment("public"); + task.setPrivateComment("private"); + + JsonApiResponse response = new JsonApiResponse(); + response.setEntity(task); + + QuerySpec bottomQuerySpec = new QuerySpec(BottomTask.class); + bottomQuerySpec.includeField(PathSpec.of("end")); + QuerySpec middleQuerySpec = new QuerySpec(MiddleTask.class); + middleQuerySpec.includeField(PathSpec.of("publicComment")); + final QuerySpec topQuerySpec = new QuerySpec(TopTask.class); + topQuerySpec.includeField(PathSpec.of("name")); + + bottomQuerySpec.setNestedSpecs(Arrays.asList(topQuerySpec, middleQuerySpec)); + + Document document = mapper.toDocument(response, toAdapter(bottomQuerySpec), mappingConfig).get(); + Resource resource = document.getSingleData().get(); + Assert.assertEquals("2", resource.getId()); + Assert.assertEquals("bottomTask", resource.getType()); + Assert.assertNull(resource.getAttributes().get("category")); + Assert.assertNull(resource.getAttributes().get("recurring")); + Assert.assertNull(resource.getAttributes().get("privateComment")); + Assert.assertNotNull(resource.getAttributes().get("name")); + Assert.assertNotNull(resource.getAttributes().get("end")); + Assert.assertNotNull(resource.getAttributes().get("publicComment")); + Assert.assertEquals("sample task", resource.getAttributes().get("name").asText()); + Assert.assertEquals("next month", resource.getAttributes().get("end").asText()); + Assert.assertEquals("public", resource.getAttributes().get("publicComment").asText()); + } + @Test public void testAttributesOrdering() { Task task = createTask(3, "sample task"); @@ -690,6 +728,13 @@ private LazyTask createLazyTask(long id) { return task; } + private BottomTask createTreeTask(long id, String name) { + BottomTask task = new BottomTask(); + task.setId(id); + task.setName(name); + return task; + } + public static class TestLinksInformation implements LinksInformation { public Link value; diff --git a/crnk-core/src/test/java/io/crnk/core/mock/models/BottomTask.java b/crnk-core/src/test/java/io/crnk/core/mock/models/BottomTask.java new file mode 100644 index 000000000..5537001b7 --- /dev/null +++ b/crnk-core/src/test/java/io/crnk/core/mock/models/BottomTask.java @@ -0,0 +1,27 @@ +package io.crnk.core.mock.models; + +import io.crnk.core.resource.annotations.JsonApiResource; + +@JsonApiResource(type = "bottomTask", resourcePath = "treeTasks") +public class BottomTask extends MiddleTask { + + private boolean recurring; + + private String end; + + public boolean isRecurring() { + return recurring; + } + + public void setRecurring(final boolean recurring) { + this.recurring = recurring; + } + + public String getEnd() { + return end; + } + + public void setEnd(final String end) { + this.end = end; + } +} \ No newline at end of file diff --git a/crnk-core/src/test/java/io/crnk/core/mock/models/MiddleTask.java b/crnk-core/src/test/java/io/crnk/core/mock/models/MiddleTask.java new file mode 100644 index 000000000..099cd2c65 --- /dev/null +++ b/crnk-core/src/test/java/io/crnk/core/mock/models/MiddleTask.java @@ -0,0 +1,27 @@ +package io.crnk.core.mock.models; + +import io.crnk.core.resource.annotations.JsonApiResource; + +@JsonApiResource(type = "middleTask", subTypes = BottomTask.class, resourcePath = "treeTasks") +public abstract class MiddleTask extends TopTask { + + private String publicComment; + + private String privateComment; + + public String getPublicComment() { + return publicComment; + } + + public void setPublicComment(final String publicComment) { + this.publicComment = publicComment; + } + + public String getPrivateComment() { + return privateComment; + } + + public void setPrivateComment(final String privateComment) { + this.privateComment = privateComment; + } +} \ No newline at end of file diff --git a/crnk-core/src/test/java/io/crnk/core/mock/models/TopTask.java b/crnk-core/src/test/java/io/crnk/core/mock/models/TopTask.java new file mode 100644 index 000000000..bb8a77df0 --- /dev/null +++ b/crnk-core/src/test/java/io/crnk/core/mock/models/TopTask.java @@ -0,0 +1,40 @@ +package io.crnk.core.mock.models; + +import io.crnk.core.resource.annotations.JsonApiId; +import io.crnk.core.resource.annotations.JsonApiResource; + +@JsonApiResource(type = "topTask", subTypes = MiddleTask.class, resourcePath = "treeTasks") +public abstract class TopTask { + + @JsonApiId + private Long id; + + private String name; + + private String category; + + public Long getId() { + return id; + } + + public TopTask setId(Long id) { + this.id = id; + return this; + } + + public String getName() { + return name; + } + + public void setName(@SuppressWarnings("SameParameterValue") String name) { + this.name = name; + } + + public String getCategory() { + return category; + } + + public void setCategory(final String category) { + this.category = category; + } +} diff --git a/crnk-core/src/test/java/io/crnk/core/mock/repository/TopTaskRepository.java b/crnk-core/src/test/java/io/crnk/core/mock/repository/TopTaskRepository.java new file mode 100644 index 000000000..cf53e61e0 --- /dev/null +++ b/crnk-core/src/test/java/io/crnk/core/mock/repository/TopTaskRepository.java @@ -0,0 +1,18 @@ +package io.crnk.core.mock.repository; + +import io.crnk.core.mock.models.TopTask; +import io.crnk.core.queryspec.QuerySpec; +import io.crnk.core.repository.ReadOnlyResourceRepositoryBase; +import io.crnk.core.resource.list.ResourceList; + +public class TopTaskRepository extends ReadOnlyResourceRepositoryBase { + + public TopTaskRepository() { + super(TopTask.class); + } + + @Override + public ResourceList findAll(final QuerySpec querySpec) { + return null; + } +} \ No newline at end of file