From ae0c83c6e9e89ed9182d99cdbcc0ff1e8782b30a Mon Sep 17 00:00:00 2001 From: Daniel Gerhardt <code@dgerhardt.net> Date: Sun, 12 Aug 2018 15:15:24 +0200 Subject: [PATCH] Refactor and fix sub type handling for Content and Answer We now use the format property instead as type ID for Jackson. A custom TypeIdResolver is used for the mapping. --- .../java/de/thm/arsnova/entities/Answer.java | 30 +++++++++-- .../java/de/thm/arsnova/entities/Content.java | 16 ++++-- .../serialization/CouchDbDocumentModule.java | 12 ----- .../FormatAnswerTypeIdResolver.java | 53 +++++++++++++++++++ .../FormatContentTypeIdResolver.java | 51 ++++++++++++++++++ src/main/resources/couchdb/Answer.design.js | 22 ++++---- src/main/resources/couchdb/Content.design.js | 6 +-- 7 files changed, 157 insertions(+), 33 deletions(-) create mode 100644 src/main/java/de/thm/arsnova/entities/serialization/FormatAnswerTypeIdResolver.java create mode 100644 src/main/java/de/thm/arsnova/entities/serialization/FormatContentTypeIdResolver.java diff --git a/src/main/java/de/thm/arsnova/entities/Answer.java b/src/main/java/de/thm/arsnova/entities/Answer.java index f3a251d33..c81adc7e8 100644 --- a/src/main/java/de/thm/arsnova/entities/Answer.java +++ b/src/main/java/de/thm/arsnova/entities/Answer.java @@ -2,6 +2,8 @@ package de.thm.arsnova.entities; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonView; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; +import de.thm.arsnova.entities.serialization.FormatAnswerTypeIdResolver; import de.thm.arsnova.entities.serialization.View; import org.springframework.core.style.ToStringCreator; @@ -9,14 +11,17 @@ import java.util.Map; import java.util.Objects; @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type" + use = JsonTypeInfo.Id.CUSTOM, + property = "format", + visible = true, + defaultImpl = Answer.class ) -public abstract class Answer extends Entity { +@JsonTypeIdResolver(FormatAnswerTypeIdResolver.class) +public class Answer extends Entity { private String contentId; private String roomId; private String creatorId; + private Content.Format format; private int round; private Map<String, Map<String, ?>> extensions; @@ -49,6 +54,16 @@ public abstract class Answer extends Entity { this.creatorId = creatorId; } + @JsonView({View.Persistence.class, View.Public.class}) + public Content.Format getFormat() { + return format; + } + + @JsonView({View.Persistence.class, View.Public.class}) + public void setFormat(final Content.Format format) { + this.format = format; + } + @JsonView({View.Persistence.class, View.Public.class}) public int getRound() { return round; @@ -69,6 +84,12 @@ public abstract class Answer extends Entity { this.extensions = extensions; } + @JsonView(View.Persistence.class) + @Override + public Class<? extends Entity> getType() { + return Answer.class; + } + /** * {@inheritDoc} * @@ -97,6 +118,7 @@ public abstract class Answer extends Entity { .append("contentId", contentId) .append("roomId", roomId) .append("creatorId", creatorId) + .append("format", format) .append("round", round); } } diff --git a/src/main/java/de/thm/arsnova/entities/Content.java b/src/main/java/de/thm/arsnova/entities/Content.java index 45ad0e573..11bf0c4f6 100644 --- a/src/main/java/de/thm/arsnova/entities/Content.java +++ b/src/main/java/de/thm/arsnova/entities/Content.java @@ -2,6 +2,8 @@ package de.thm.arsnova.entities; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonView; +import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver; +import de.thm.arsnova.entities.serialization.FormatContentTypeIdResolver; import de.thm.arsnova.entities.serialization.View; import org.springframework.core.style.ToStringCreator; @@ -12,10 +14,12 @@ import java.util.Objects; import java.util.Set; @JsonTypeInfo( - use = JsonTypeInfo.Id.NAME, - include = JsonTypeInfo.As.EXISTING_PROPERTY, - property = "type" + use = JsonTypeInfo.Id.CUSTOM, + property = "format", + visible = true, + defaultImpl = Content.class ) +@JsonTypeIdResolver(FormatContentTypeIdResolver.class) public class Content extends Entity { public enum Format { CHOICE, @@ -227,6 +231,12 @@ public class Content extends Entity { this.abstentionsAllowed = abstentionsAllowed; } + @JsonView(View.Persistence.class) + @Override + public Class<? extends Entity> getType() { + return Content.class; + } + /** * {@inheritDoc} * diff --git a/src/main/java/de/thm/arsnova/entities/serialization/CouchDbDocumentModule.java b/src/main/java/de/thm/arsnova/entities/serialization/CouchDbDocumentModule.java index a99adbf97..bf6cfd26c 100644 --- a/src/main/java/de/thm/arsnova/entities/serialization/CouchDbDocumentModule.java +++ b/src/main/java/de/thm/arsnova/entities/serialization/CouchDbDocumentModule.java @@ -17,14 +17,8 @@ */ package de.thm.arsnova.entities.serialization; -import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.databind.module.SimpleModule; -import de.thm.arsnova.entities.Answer; -import de.thm.arsnova.entities.ChoiceAnswer; -import de.thm.arsnova.entities.ChoiceQuestionContent; -import de.thm.arsnova.entities.Content; import de.thm.arsnova.entities.Entity; -import de.thm.arsnova.entities.TextAnswer; public class CouchDbDocumentModule extends SimpleModule { public CouchDbDocumentModule() { @@ -35,11 +29,5 @@ public class CouchDbDocumentModule extends SimpleModule { public void setupModule(SetupContext context) { context.setMixInAnnotations(Entity.class, CouchDbDocumentMixIn.class); context.setMixInAnnotations(de.thm.arsnova.entities.migration.v2.Entity.class, CouchDbDocumentV2MixIn.class); - context.registerSubtypes( - new NamedType(Content.class, Content.class.getSimpleName()), - new NamedType(ChoiceQuestionContent.class, ChoiceQuestionContent.class.getSimpleName()), - new NamedType(Answer.class, Answer.class.getSimpleName()), - new NamedType(ChoiceAnswer.class, ChoiceAnswer.class.getSimpleName()), - new NamedType(TextAnswer.class, TextAnswer.class.getSimpleName())); } } diff --git a/src/main/java/de/thm/arsnova/entities/serialization/FormatAnswerTypeIdResolver.java b/src/main/java/de/thm/arsnova/entities/serialization/FormatAnswerTypeIdResolver.java new file mode 100644 index 000000000..5e2f3335b --- /dev/null +++ b/src/main/java/de/thm/arsnova/entities/serialization/FormatAnswerTypeIdResolver.java @@ -0,0 +1,53 @@ +package de.thm.arsnova.entities.serialization; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.DatabindContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase; +import com.fasterxml.jackson.databind.type.TypeFactory; +import de.thm.arsnova.entities.Answer; +import de.thm.arsnova.entities.ChoiceAnswer; +import de.thm.arsnova.entities.Content; +import de.thm.arsnova.entities.TextAnswer; + +import java.io.IOException; + +public class FormatAnswerTypeIdResolver extends TypeIdResolverBase { + @Override + public String idFromValue(final Object value) { + if (value instanceof Answer) { + return ((Answer) value).getFormat().toString(); + } else { + throw new IllegalArgumentException("Unsupported type."); + } + } + + @Override + public String idFromValueAndType(final Object value, final Class<?> suggestedType) { + return idFromValue(value); + } + + @Override + public JavaType typeFromId(final DatabindContext context, final String id) throws IOException { + Content.Format format = Content.Format.valueOf(id); + switch (format) { + case BINARY: + return TypeFactory.defaultInstance().constructType(ChoiceAnswer.class); + case CHOICE: + return TypeFactory.defaultInstance().constructType(ChoiceAnswer.class); + case NUMBER: + return TypeFactory.defaultInstance().constructType(ChoiceAnswer.class); + case SCALE: + return TypeFactory.defaultInstance().constructType(ChoiceAnswer.class); + case TEXT: + return TypeFactory.defaultInstance().constructType(TextAnswer.class); + default: + throw new IllegalArgumentException("Unsupported type ID."); + } + } + + @Override + public JsonTypeInfo.Id getMechanism() { + return JsonTypeInfo.Id.CUSTOM; + } +} diff --git a/src/main/java/de/thm/arsnova/entities/serialization/FormatContentTypeIdResolver.java b/src/main/java/de/thm/arsnova/entities/serialization/FormatContentTypeIdResolver.java new file mode 100644 index 000000000..2b2834460 --- /dev/null +++ b/src/main/java/de/thm/arsnova/entities/serialization/FormatContentTypeIdResolver.java @@ -0,0 +1,51 @@ +package de.thm.arsnova.entities.serialization; + +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.DatabindContext; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.jsontype.impl.TypeIdResolverBase; +import com.fasterxml.jackson.databind.type.TypeFactory; +import de.thm.arsnova.entities.ChoiceQuestionContent; +import de.thm.arsnova.entities.Content; + +import java.io.IOException; + +public class FormatContentTypeIdResolver extends TypeIdResolverBase { + @Override + public String idFromValue(final Object value) { + if (value instanceof Content) { + return ((Content) value).getFormat().toString(); + } else { + throw new IllegalArgumentException("Unsupported type."); + } + } + + @Override + public String idFromValueAndType(final Object value, final Class<?> suggestedType) { + return idFromValue(value); + } + + @Override + public JavaType typeFromId(final DatabindContext context, final String id) throws IOException { + Content.Format format = Content.Format.valueOf(id); + switch (format) { + case BINARY: + return TypeFactory.defaultInstance().constructType(ChoiceQuestionContent.class); + case CHOICE: + return TypeFactory.defaultInstance().constructType(ChoiceQuestionContent.class); + case NUMBER: + return TypeFactory.defaultInstance().constructType(ChoiceQuestionContent.class); + case SCALE: + return TypeFactory.defaultInstance().constructType(ChoiceQuestionContent.class); + case TEXT: + return TypeFactory.defaultInstance().constructType(Content.class); + default: + throw new IllegalArgumentException("Unsupported type ID."); + } + } + + @Override + public JsonTypeInfo.Id getMechanism() { + return JsonTypeInfo.Id.CUSTOM; + } +} diff --git a/src/main/resources/couchdb/Answer.design.js b/src/main/resources/couchdb/Answer.design.js index 88115413e..3e89d47b1 100644 --- a/src/main/resources/couchdb/Answer.design.js +++ b/src/main/resources/couchdb/Answer.design.js @@ -4,7 +4,7 @@ var designDoc = { "views": { "by_id": { "map": function (doc) { - if (["Answer", "ChoiceAnswer", "TextAnswer"].indexOf(doc.type) !== -1) { + if (doc.type === "Answer") { emit(doc._id, {_rev: doc._rev}); } }, @@ -12,14 +12,14 @@ var designDoc = { }, "by_contentid": { "map": function (doc) { - if (["Answer", "ChoiceAnswer", "TextAnswer"].indexOf(doc.type) !== -1) { + if (doc.type === "Answer") { emit(doc.contentId, {_rev: doc._rev}); } } }, "by_contentid_round_body_subject": { "map": function (doc) { - if (["Answer", "ChoiceAnswer", "TextAnswer"].indexOf(doc.type) !== -1) { + if (doc.type === "Answer") { emit([doc.contentId, doc.round, doc.abstention, doc.body, doc.subject, doc.successfulFreeTextAnswer], {_rev: doc._rev}); } }, @@ -27,7 +27,7 @@ var designDoc = { }, "by_contentid_round_selectedchoiceindexes": { "map": function (doc) { - if (["Answer", "ChoiceAnswer", "TextAnswer"].indexOf(doc.type) !== -1) { + if (doc.type === "Answer") { emit([doc.contentId, doc.round, doc.selectedChoiceIndexes], {_rev: doc._rev}); } }, @@ -35,21 +35,21 @@ var designDoc = { }, "by_contentid_creationtimestamp": { "map": function (doc) { - if (["Answer", "ChoiceAnswer", "TextAnswer"].indexOf(doc.type) !== -1) { + if (doc.type === "Answer") { emit([doc.contentId, doc.creationTimestamp], {_rev: doc._rev}); } } }, "by_contentid_creatorid_round": { "map": function (doc) { - if (["Answer", "ChoiceAnswer", "TextAnswer"].indexOf(doc.type) !== -1) { + if (doc.type === "Answer") { emit([doc.contentId, doc.creatorId, doc.round], {_rev: doc._rev}); } } }, "by_roomid": { "map": function (doc) { - if (["Answer", "ChoiceAnswer", "TextAnswer"].indexOf(doc.type) !== -1) { + if (doc.type === "Answer") { emit(doc.roomId, {_rev: doc._rev}); } }, @@ -57,7 +57,7 @@ var designDoc = { }, "by_roomid_variant": { "map": function (doc) { - if (["Answer", "ChoiceAnswer", "TextAnswer"].indexOf(doc.type) !== -1) { + if (doc.type === "Answer") { emit([doc.roomId, doc.questionVariant], {_rev: doc._rev}); } }, @@ -65,21 +65,21 @@ var designDoc = { }, "by_creatorid_roomid": { "map": function (doc) { - if (["Answer", "ChoiceAnswer", "TextAnswer"].indexOf(doc.type) !== -1) { + if (doc.type === "Answer") { emit([doc.creatorId, doc.roomId], {_rev: doc._rev}); } } }, "contentid_by_creatorid_roomid_variant": { "map": function (doc) { - if (["Answer", "ChoiceAnswer", "TextAnswer"].indexOf(doc.type) !== -1) { + if (doc.type === "Answer") { emit([doc.user, doc.roomId, doc.questionVariant], doc.contentId); } } }, "contentid_round_by_creatorid_roomid_variant": { "map": function (doc) { - if (["Answer", "ChoiceAnswer", "TextAnswer"].indexOf(doc.type) !== -1) { + if (doc.type === "Answer") { emit([doc.creatorId, doc.roomId, doc.questionVariant], [doc.contentId, doc.round]); } } diff --git a/src/main/resources/couchdb/Content.design.js b/src/main/resources/couchdb/Content.design.js index 9c130da96..5b09c23f6 100644 --- a/src/main/resources/couchdb/Content.design.js +++ b/src/main/resources/couchdb/Content.design.js @@ -4,7 +4,7 @@ var designDoc = { "views": { "by_id": { "map": function (doc) { - if (["Content", "ChoiceQuestionContent"].indexOf(doc.type) !== -1) { + if (doc.type === "Content") { emit(doc._id, {_rev: doc._rev}); } }, @@ -12,7 +12,7 @@ var designDoc = { }, "by_roomid": { "map": function (doc) { - if (["Content", "ChoiceQuestionContent"].indexOf(doc.type) !== -1) { + if (doc.type === "Content") { emit(doc.roomId, {_rev: doc._rev}); } }, @@ -20,7 +20,7 @@ var designDoc = { }, "by_roomid_group_locked": { "map": function (doc) { - if (["Content", "ChoiceQuestionContent"].indexOf(doc.type) !== -1) { + if (doc.type === "Content") { emit([doc.roomId, doc.group, doc.locked, doc.subject, doc.body.substr(0, 16)], {_rev: doc._rev}); } }, -- GitLab