From 2733b6d52d93434a6b88c0264802315bcbd6c3f9 Mon Sep 17 00:00:00 2001 From: "alexis.drai@etu.uca.fr" Date: Wed, 31 May 2023 14:18:55 +0200 Subject: [PATCH 1/6] :truck: Organize packages further --- .../fr/uca/iut/codecs/move/MoveCodec.java | 2 +- .../iut/codecs/pokemong/PokemongCodec.java | 4 +- .../uca/iut/codecs/trainer/TrainerCodec.java | 2 +- .../fr/uca/iut/codecs/type/TypeCodecUtil.java | 2 +- src/main/java/fr/uca/iut/entities/Move.java | 109 ++++++++--------- .../java/fr/uca/iut/entities/Pokemong.java | 2 + .../java/fr/uca/iut/entities/Trainer.java | 2 + .../{ => denormalized}/PokemongMove.java | 34 +++--- .../{ => denormalized}/TrainerPokemong.java | 3 +- .../uca/iut/entities/{ => embedded}/Type.java | 110 +++++++++--------- .../fr/uca/iut/services/PokemongService.java | 3 + .../fr/uca/iut/services/TrainerService.java | 2 +- 12 files changed, 143 insertions(+), 132 deletions(-) rename src/main/java/fr/uca/iut/entities/{ => denormalized}/PokemongMove.java (73%) rename src/main/java/fr/uca/iut/entities/{ => denormalized}/TrainerPokemong.java (87%) rename src/main/java/fr/uca/iut/entities/{ => embedded}/Type.java (94%) diff --git a/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java b/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java index 13b7ec8..0728262 100644 --- a/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java +++ b/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java @@ -4,7 +4,7 @@ import com.mongodb.MongoClientSettings; import fr.uca.iut.codecs.GenericCodec; import fr.uca.iut.codecs.type.TypeCodecUtil; import fr.uca.iut.entities.Move; -import fr.uca.iut.entities.Type; +import fr.uca.iut.entities.embedded.Type; import fr.uca.iut.utils.enums.MoveCategoryName; import org.bson.BsonReader; import org.bson.BsonWriter; diff --git a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java index 4d65c23..b74f044 100644 --- a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java +++ b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java @@ -4,8 +4,8 @@ import com.mongodb.MongoClientSettings; import fr.uca.iut.codecs.GenericCodec; import fr.uca.iut.codecs.type.TypeCodecUtil; import fr.uca.iut.entities.Pokemong; -import fr.uca.iut.entities.PokemongMove; -import fr.uca.iut.entities.Type; +import fr.uca.iut.entities.denormalized.PokemongMove; +import fr.uca.iut.entities.embedded.Type; import fr.uca.iut.utils.enums.PokemongName; import org.bson.BsonReader; import org.bson.BsonWriter; diff --git a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java index f9c0041..3ca1807 100644 --- a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java +++ b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java @@ -3,7 +3,7 @@ package fr.uca.iut.codecs.trainer; import com.mongodb.MongoClientSettings; import fr.uca.iut.codecs.GenericCodec; import fr.uca.iut.entities.Trainer; -import fr.uca.iut.entities.TrainerPokemong; +import fr.uca.iut.entities.denormalized.TrainerPokemong; import fr.uca.iut.utils.enums.PokemongName; import org.bson.BsonReader; import org.bson.BsonWriter; diff --git a/src/main/java/fr/uca/iut/codecs/type/TypeCodecUtil.java b/src/main/java/fr/uca/iut/codecs/type/TypeCodecUtil.java index bd45965..89cd1e8 100644 --- a/src/main/java/fr/uca/iut/codecs/type/TypeCodecUtil.java +++ b/src/main/java/fr/uca/iut/codecs/type/TypeCodecUtil.java @@ -1,6 +1,6 @@ package fr.uca.iut.codecs.type; -import fr.uca.iut.entities.Type; +import fr.uca.iut.entities.embedded.Type; import fr.uca.iut.utils.enums.TypeName; import org.bson.Document; diff --git a/src/main/java/fr/uca/iut/entities/Move.java b/src/main/java/fr/uca/iut/entities/Move.java index da98c37..b25cd45 100644 --- a/src/main/java/fr/uca/iut/entities/Move.java +++ b/src/main/java/fr/uca/iut/entities/Move.java @@ -1,55 +1,56 @@ -package fr.uca.iut.entities; - -import fr.uca.iut.utils.enums.MoveCategoryName; - -public class Move extends GenericEntity { - public static final String COLLECTION_NAME = "moves"; - - private String name; - private MoveCategoryName category; - private Integer power; - private Integer accuracy; - private Type type; - - public Move() {} - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public MoveCategoryName getCategory() { - return category; - } - - public void setCategory(MoveCategoryName category) { - this.category = category; - } - - public Integer getPower() { - return power; - } - - public void setPower(Integer power) { - this.power = power; - } - - public Integer getAccuracy() { - return accuracy; - } - - public void setAccuracy(Integer accuracy) { - this.accuracy = accuracy; - } - - public Type getType() { - return type; - } - - public void setType(Type type) { - this.type = type; - } +package fr.uca.iut.entities; + +import fr.uca.iut.entities.embedded.Type; +import fr.uca.iut.utils.enums.MoveCategoryName; + +public class Move extends GenericEntity { + public static final String COLLECTION_NAME = "moves"; + + private String name; + private MoveCategoryName category; + private Integer power; + private Integer accuracy; + private Type type; + + public Move() {} + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public MoveCategoryName getCategory() { + return category; + } + + public void setCategory(MoveCategoryName category) { + this.category = category; + } + + public Integer getPower() { + return power; + } + + public void setPower(Integer power) { + this.power = power; + } + + public Integer getAccuracy() { + return accuracy; + } + + public void setAccuracy(Integer accuracy) { + this.accuracy = accuracy; + } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } } \ No newline at end of file diff --git a/src/main/java/fr/uca/iut/entities/Pokemong.java b/src/main/java/fr/uca/iut/entities/Pokemong.java index 1acd9e4..3645a6e 100644 --- a/src/main/java/fr/uca/iut/entities/Pokemong.java +++ b/src/main/java/fr/uca/iut/entities/Pokemong.java @@ -1,6 +1,8 @@ package fr.uca.iut.entities; import com.mongodb.lang.Nullable; +import fr.uca.iut.entities.denormalized.PokemongMove; +import fr.uca.iut.entities.embedded.Type; import fr.uca.iut.utils.enums.PokemongName; import java.time.LocalDate; diff --git a/src/main/java/fr/uca/iut/entities/Trainer.java b/src/main/java/fr/uca/iut/entities/Trainer.java index 6586349..ea2fef7 100644 --- a/src/main/java/fr/uca/iut/entities/Trainer.java +++ b/src/main/java/fr/uca/iut/entities/Trainer.java @@ -1,5 +1,7 @@ package fr.uca.iut.entities; +import fr.uca.iut.entities.denormalized.TrainerPokemong; + import java.time.LocalDate; import java.util.Collections; import java.util.List; diff --git a/src/main/java/fr/uca/iut/entities/PokemongMove.java b/src/main/java/fr/uca/iut/entities/denormalized/PokemongMove.java similarity index 73% rename from src/main/java/fr/uca/iut/entities/PokemongMove.java rename to src/main/java/fr/uca/iut/entities/denormalized/PokemongMove.java index 13c4c31..a9da590 100644 --- a/src/main/java/fr/uca/iut/entities/PokemongMove.java +++ b/src/main/java/fr/uca/iut/entities/denormalized/PokemongMove.java @@ -1,16 +1,18 @@ -package fr.uca.iut.entities; - -public class PokemongMove extends GenericEntity { - - private String name; - - public PokemongMove() {} - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } -} +package fr.uca.iut.entities.denormalized; + +import fr.uca.iut.entities.GenericEntity; + +public class PokemongMove extends GenericEntity { + + private String name; + + public PokemongMove() {} + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/main/java/fr/uca/iut/entities/TrainerPokemong.java b/src/main/java/fr/uca/iut/entities/denormalized/TrainerPokemong.java similarity index 87% rename from src/main/java/fr/uca/iut/entities/TrainerPokemong.java rename to src/main/java/fr/uca/iut/entities/denormalized/TrainerPokemong.java index f63fcb5..b44bb88 100644 --- a/src/main/java/fr/uca/iut/entities/TrainerPokemong.java +++ b/src/main/java/fr/uca/iut/entities/denormalized/TrainerPokemong.java @@ -1,6 +1,7 @@ -package fr.uca.iut.entities; +package fr.uca.iut.entities.denormalized; import com.mongodb.lang.Nullable; +import fr.uca.iut.entities.GenericEntity; import fr.uca.iut.utils.enums.PokemongName; public class TrainerPokemong extends GenericEntity { diff --git a/src/main/java/fr/uca/iut/entities/Type.java b/src/main/java/fr/uca/iut/entities/embedded/Type.java similarity index 94% rename from src/main/java/fr/uca/iut/entities/Type.java rename to src/main/java/fr/uca/iut/entities/embedded/Type.java index f9e3022..5af9d0d 100644 --- a/src/main/java/fr/uca/iut/entities/Type.java +++ b/src/main/java/fr/uca/iut/entities/embedded/Type.java @@ -1,56 +1,56 @@ -package fr.uca.iut.entities; - -import fr.uca.iut.utils.enums.TypeName; - -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -public class Type { - - private TypeName name; - private List weakAgainst; - private List effectiveAgainst; - - public Type() {} - - public TypeName getName() { - return name; - } - - public void setName(TypeName name) { - this.name = name; - } - - public List getWeakAgainst() { - return Collections.unmodifiableList(weakAgainst); - } - - public void setWeakAgainst(List weakAgainst) { - this.weakAgainst = weakAgainst; - } - - public List getEffectiveAgainst() { - return Collections.unmodifiableList(effectiveAgainst); - } - - public void setEffectiveAgainst(List effectiveAgainst) { - this.effectiveAgainst = effectiveAgainst; - } - - @Override - public int hashCode() { - return Objects.hash(name, weakAgainst, effectiveAgainst); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Type type = (Type) o; - return Objects.equals(name, type.name) && - Objects.equals(weakAgainst, type.weakAgainst) && - Objects.equals(effectiveAgainst, type.effectiveAgainst); - } - +package fr.uca.iut.entities.embedded; + +import fr.uca.iut.utils.enums.TypeName; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class Type { + + private TypeName name; + private List weakAgainst; + private List effectiveAgainst; + + public Type() {} + + public TypeName getName() { + return name; + } + + public void setName(TypeName name) { + this.name = name; + } + + public List getWeakAgainst() { + return Collections.unmodifiableList(weakAgainst); + } + + public void setWeakAgainst(List weakAgainst) { + this.weakAgainst = weakAgainst; + } + + public List getEffectiveAgainst() { + return Collections.unmodifiableList(effectiveAgainst); + } + + public void setEffectiveAgainst(List effectiveAgainst) { + this.effectiveAgainst = effectiveAgainst; + } + + @Override + public int hashCode() { + return Objects.hash(name, weakAgainst, effectiveAgainst); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Type type = (Type) o; + return Objects.equals(name, type.name) && + Objects.equals(weakAgainst, type.weakAgainst) && + Objects.equals(effectiveAgainst, type.effectiveAgainst); + } + } \ No newline at end of file diff --git a/src/main/java/fr/uca/iut/services/PokemongService.java b/src/main/java/fr/uca/iut/services/PokemongService.java index ae9afd9..42c6d31 100644 --- a/src/main/java/fr/uca/iut/services/PokemongService.java +++ b/src/main/java/fr/uca/iut/services/PokemongService.java @@ -2,6 +2,9 @@ package fr.uca.iut.services; import com.mongodb.lang.Nullable; import fr.uca.iut.entities.*; +import fr.uca.iut.entities.denormalized.PokemongMove; +import fr.uca.iut.entities.denormalized.TrainerPokemong; +import fr.uca.iut.entities.embedded.Type; import fr.uca.iut.repositories.PokemongRepository; import fr.uca.iut.utils.StringUtils; import fr.uca.iut.utils.enums.PokemongName; diff --git a/src/main/java/fr/uca/iut/services/TrainerService.java b/src/main/java/fr/uca/iut/services/TrainerService.java index 65d8a88..5817a99 100644 --- a/src/main/java/fr/uca/iut/services/TrainerService.java +++ b/src/main/java/fr/uca/iut/services/TrainerService.java @@ -3,7 +3,7 @@ package fr.uca.iut.services; import com.mongodb.lang.Nullable; import fr.uca.iut.entities.Pokemong; import fr.uca.iut.entities.Trainer; -import fr.uca.iut.entities.TrainerPokemong; +import fr.uca.iut.entities.denormalized.TrainerPokemong; import fr.uca.iut.repositories.TrainerRepository; import fr.uca.iut.utils.StringUtils; import fr.uca.iut.utils.exceptions.NonValidEntityException; -- 2.36.3 From cf46b9c44dded6aa0f46f405448517a05f86ee52 Mon Sep 17 00:00:00 2001 From: "alexis.drai@etu.uca.fr" Date: Wed, 31 May 2023 14:58:32 +0200 Subject: [PATCH 2/6] :rotating_light: Add some annotations to help with `null` --- .../java/fr/uca/iut/codecs/GenericCodec.java | 5 +- .../fr/uca/iut/codecs/move/MoveCodec.java | 156 +++++++++--------- .../iut/codecs/pokemong/PokemongCodec.java | 3 +- .../pokemong/PokemongCodecProvider.java | 3 +- .../uca/iut/codecs/trainer/TrainerCodec.java | 3 +- .../codecs/trainer/TrainerCodecProvider.java | 3 +- .../fr/uca/iut/services/PokemongService.java | 2 +- 7 files changed, 90 insertions(+), 85 deletions(-) diff --git a/src/main/java/fr/uca/iut/codecs/GenericCodec.java b/src/main/java/fr/uca/iut/codecs/GenericCodec.java index 25faad3..ed21526 100644 --- a/src/main/java/fr/uca/iut/codecs/GenericCodec.java +++ b/src/main/java/fr/uca/iut/codecs/GenericCodec.java @@ -9,6 +9,7 @@ import org.bson.codecs.CollectibleCodec; import org.bson.codecs.DecoderContext; import org.bson.codecs.EncoderContext; import org.bson.types.ObjectId; +import org.jetbrains.annotations.NotNull; public abstract class GenericCodec implements CollectibleCodec { @@ -27,12 +28,12 @@ public abstract class GenericCodec implements Collectib } @Override - public boolean documentHasId(T document) { + public boolean documentHasId(@NotNull T document) { return document.getId() != null; } @Override - public BsonValue getDocumentId(T document) { + public BsonValue getDocumentId(@NotNull T document) { return new BsonObjectId(new ObjectId(document.getId())); } diff --git a/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java b/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java index 0728262..ba61209 100644 --- a/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java +++ b/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java @@ -1,78 +1,78 @@ -package fr.uca.iut.codecs.move; - -import com.mongodb.MongoClientSettings; -import fr.uca.iut.codecs.GenericCodec; -import fr.uca.iut.codecs.type.TypeCodecUtil; -import fr.uca.iut.entities.Move; -import fr.uca.iut.entities.embedded.Type; -import fr.uca.iut.utils.enums.MoveCategoryName; -import org.bson.BsonReader; -import org.bson.BsonWriter; -import org.bson.Document; -import org.bson.codecs.Codec; -import org.bson.codecs.DecoderContext; -import org.bson.codecs.EncoderContext; -import org.bson.types.ObjectId; - -public class MoveCodec extends GenericCodec { - private final Codec documentCodec; - - public MoveCodec() { - this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() - .get(Document.class); - } - - @Override - public void encode(BsonWriter writer, Move move, EncoderContext encoderContext) { - Document doc = new Document(); - - doc.put("_id", new ObjectId(move.getId())); - - doc.put("name", move.getName()); - - doc.put("category", move.getCategory()); - - doc.put("power", move.getPower()); - - doc.put("accuracy", move.getAccuracy()); - - Type moveType = move.getType(); - Document typeDoc = new Document(); - typeDoc.put("name", - moveType.getName() - .toString()); - typeDoc.put("weakAgainst", moveType.getWeakAgainst()); - typeDoc.put("effectiveAgainst", moveType.getEffectiveAgainst()); - doc.put("type", typeDoc); - - documentCodec.encode(writer, doc, encoderContext); - } - - @Override - public Class getEncoderClass() { - return Move.class; - } - - @Override - public Move decode(BsonReader reader, DecoderContext decoderContext) { - Document document = documentCodec.decode(reader, decoderContext); - Move move = new Move(); - - move.setId(document.getObjectId("_id") - .toString()); - - move.setName(document.getString("name")); - - move.setCategory(MoveCategoryName.valueOf(document.getString("category"))); - - move.setPower(document.getInteger("power")); - - move.setAccuracy(document.getInteger("accuracy")); - - Document typeDoc = (Document) document.get("type"); - - move.setType(TypeCodecUtil.extractType(typeDoc)); - - return move; - } -} +package fr.uca.iut.codecs.move; + +import com.mongodb.MongoClientSettings; +import fr.uca.iut.codecs.GenericCodec; +import fr.uca.iut.codecs.type.TypeCodecUtil; +import fr.uca.iut.entities.Move; +import fr.uca.iut.entities.embedded.Type; +import fr.uca.iut.utils.enums.MoveCategoryName; +import org.bson.BsonReader; +import org.bson.BsonWriter; +import org.bson.Document; +import org.bson.codecs.Codec; +import org.bson.codecs.DecoderContext; +import org.bson.codecs.EncoderContext; +import org.bson.types.ObjectId; + +public class MoveCodec extends GenericCodec { + private final Codec documentCodec; + + public MoveCodec() { + this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() + .get(Document.class); + } + + @Override + public void encode(BsonWriter writer, @NotNull Move move, EncoderContext encoderContext) { + Document doc = new Document(); + + doc.put("_id", new ObjectId(move.getId())); + + doc.put("name", move.getName()); + + doc.put("category", move.getCategory()); + + doc.put("power", move.getPower()); + + doc.put("accuracy", move.getAccuracy()); + + Type moveType = move.getType(); + Document typeDoc = new Document(); + typeDoc.put("name", + moveType.getName() + .toString()); + typeDoc.put("weakAgainst", moveType.getWeakAgainst()); + typeDoc.put("effectiveAgainst", moveType.getEffectiveAgainst()); + doc.put("type", typeDoc); + + documentCodec.encode(writer, doc, encoderContext); + } + + @Override + public Class getEncoderClass() { + return Move.class; + } + + @Override + public Move decode(BsonReader reader, DecoderContext decoderContext) { + Document document = documentCodec.decode(reader, decoderContext); + Move move = new Move(); + + move.setId(document.getObjectId("_id") + .toString()); + + move.setName(document.getString("name")); + + move.setCategory(MoveCategoryName.valueOf(document.getString("category"))); + + move.setPower(document.getInteger("power")); + + move.setAccuracy(document.getInteger("accuracy")); + + Document typeDoc = (Document) document.get("type"); + + move.setType(TypeCodecUtil.extractType(typeDoc)); + + return move; + } +} diff --git a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java index b74f044..a4bdd36 100644 --- a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java +++ b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java @@ -14,6 +14,7 @@ import org.bson.codecs.Codec; import org.bson.codecs.DecoderContext; import org.bson.codecs.EncoderContext; import org.bson.types.ObjectId; +import org.jetbrains.annotations.NotNull; import java.time.ZoneId; import java.util.Date; @@ -30,7 +31,7 @@ public class PokemongCodec extends GenericCodec { } @Override - public void encode(BsonWriter writer, Pokemong pokemong, EncoderContext encoderContext) { + public void encode(BsonWriter writer, @NotNull Pokemong pokemong, EncoderContext encoderContext) { Document doc = new Document(); doc.put("_id", new ObjectId(pokemong.getId())); diff --git a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodecProvider.java b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodecProvider.java index 7327c19..fb1db0d 100644 --- a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodecProvider.java +++ b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodecProvider.java @@ -5,11 +5,12 @@ import fr.uca.iut.entities.Pokemong; import org.bson.codecs.Codec; import org.bson.codecs.configuration.CodecProvider; import org.bson.codecs.configuration.CodecRegistry; +import org.jetbrains.annotations.NotNull; public class PokemongCodecProvider implements CodecProvider { @Nullable @Override - public Codec get(Class clazz, CodecRegistry registry) { + public Codec get(@NotNull Class clazz, CodecRegistry registry) { if (clazz.equals(Pokemong.class)) { return (Codec) new PokemongCodec(); } diff --git a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java index 3ca1807..d37e723 100644 --- a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java +++ b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java @@ -12,6 +12,7 @@ import org.bson.codecs.Codec; import org.bson.codecs.DecoderContext; import org.bson.codecs.EncoderContext; import org.bson.types.ObjectId; +import org.jetbrains.annotations.NotNull; import java.time.LocalDate; import java.time.ZoneId; @@ -28,7 +29,7 @@ public class TrainerCodec extends GenericCodec { } @Override - public void encode(BsonWriter writer, Trainer trainer, EncoderContext encoderContext) { + public void encode(BsonWriter writer, @NotNull Trainer trainer, EncoderContext encoderContext) { Document doc = new Document(); doc.put("_id", new ObjectId(trainer.getId())); diff --git a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodecProvider.java b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodecProvider.java index 0c3e37a..87aac26 100644 --- a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodecProvider.java +++ b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodecProvider.java @@ -5,11 +5,12 @@ import fr.uca.iut.entities.Trainer; import org.bson.codecs.Codec; import org.bson.codecs.configuration.CodecProvider; import org.bson.codecs.configuration.CodecRegistry; +import org.jetbrains.annotations.NotNull; public class TrainerCodecProvider implements CodecProvider { @Nullable @Override - public Codec get(Class clazz, CodecRegistry registry) { + public Codec get(@NotNull Class clazz, CodecRegistry registry) { if (clazz.equals(Trainer.class)) { return (Codec) new TrainerCodec(); } diff --git a/src/main/java/fr/uca/iut/services/PokemongService.java b/src/main/java/fr/uca/iut/services/PokemongService.java index 42c6d31..c80886c 100644 --- a/src/main/java/fr/uca/iut/services/PokemongService.java +++ b/src/main/java/fr/uca/iut/services/PokemongService.java @@ -193,7 +193,7 @@ public class PokemongService extends GenericService { return repository.existsById(pokemongId); } - public void batchUpdatePokemongTrainers(List trainerPokemongs, @Nullable String trainerId) { + public void batchUpdatePokemongTrainers(@NotNull List trainerPokemongs, @Nullable String trainerId) { for (TrainerPokemong trainerPokemong : trainerPokemongs) { Pokemong pokemong = getOneById(trainerPokemong.getId()); if (pokemong != null) { -- 2.36.3 From 9832343bc1e59a174243042748d44a6ac674c217 Mon Sep 17 00:00:00 2001 From: "alexis.drai@etu.uca.fr" Date: Wed, 31 May 2023 20:45:34 +0200 Subject: [PATCH 3/6] :construction: WIP make it so 2 trainers can't share 1 pokemong use sets where collection order doesn't matter and items should be unique make services call entity validation, to prevent additions and updates that are side-effects from bypassing the validator implement schema versioning and incremental migrations --- README.md | 60 ++++++- .../fr/uca/iut/codecs/move/MoveCodec.java | 44 +++++ .../iut/codecs/pokemong/PokemongCodec.java | 22 ++- .../uca/iut/codecs/trainer/TrainerCodec.java | 20 ++- .../iut/controllers/GenericController.java | 2 - .../iut/entities/GenericVersionedEntity.java | 21 +++ src/main/java/fr/uca/iut/entities/Move.java | 3 +- .../java/fr/uca/iut/entities/Pokemong.java | 14 +- .../java/fr/uca/iut/entities/Trainer.java | 20 ++- .../fr/uca/iut/entities/embedded/Type.java | 5 +- .../fr/uca/iut/services/GenericService.java | 21 ++- .../java/fr/uca/iut/services/MoveService.java | 97 +++++++---- .../fr/uca/iut/services/PokemongService.java | 160 ++++++++++-------- .../fr/uca/iut/services/TrainerService.java | 116 +++++++++---- src/main/resources/META-INF/openapi.yaml | 14 ++ 15 files changed, 449 insertions(+), 170 deletions(-) create mode 100644 src/main/java/fr/uca/iut/entities/GenericVersionedEntity.java diff --git a/README.md b/README.md index e82d5e6..c45f0a6 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,71 @@ Instructions are [here](https://clientserveur-courses.clubinfo-clermont.fr/Notat ## About -A "Pokemong" is a playful term for a `MongoDB` pocket monster. +A "Pokemong" is a playful term for a MongoDB pocket monster. -The application is developed using the Quarkus framework and uses `MongoDB` as its database. +The application is developed using the Quarkus framework and uses MongoDB as its database. -This application is a RESTful service designed to emulate a basic Pokemong management system. It allows users to perform -CRUD operations on Pokemongs, trainers, moves, and types. +This application is a RESTful service designed to emulate a basic `Pokemong` management system. It allows users to +perform +CRUD operations on `Pokemongs`, `Trainers`, `Moves`, and `Types`. ### 🗂️ DCM + Data Concept Model ### 🧬 UML Class diagram + UML Class Diagram +### NoSQL Schema Versioning Strategy + +This application uses MongoDB, a NoSQL database, which provides flexibility in our data model. While this flexibility +has +its advantages, it poses a unique challenge when we need to update our data model, specifically when we want to +introduce breaking changes in the existing schema. + +We have adopted a schema versioning strategy to overcome this challenge and manage these changes efficiently. + +#### Schema Versioning Pattern + +Schema versioning is a pattern that involves tagging each document in a collection with a version number. This version +number corresponds to the schema of the document and is used to handle schema changes in the code that reads these +documents. + +Each entity in our model extends a `GenericVersionedEntity` class, which includes a `schemaVersion` field. This field is +an integer that starts at 1 and is to be incremented by one with each schema change. Every change to the schema needs to +involve the schema version number being incremented. + +#### Incremental Document Migration + +When a document is read from the database, the version number in the document is checked. If the version number is less +than the current version, the document is updated to the current version, and the updated document is written back to +the database. This process effectively migrates the document to the current version. + +In the example of the `Move` class, the codec's `decodeV1` method handles documents with a `schemaVersion` of less +than `2`. When it reads a document with this version, it updates the `schemaVersion` to `2`, and writes the updated +document back to the database. + +```java +Move decodeV1(Document document){ + // ... + // Increment the schemaVersion to the current version + move.setSchemaVersion(2); + + // Save the updated Move object back to the database + moveRepository.persistOrUpdate(move); + // ... + } +``` + +This strategy allows for graceful schema evolution in a NoSQL environment. Instead of requiring all documents to be +migrated at once, which can be a time-consuming operation for large collections, it enables incremental document +migration. This approach also helps to avoid downtime during schema migration, as the application continues to function +correctly regardless of the document version. As documents are read, they are updated to the current schema version, +allowing the schema migration to happen gradually over time. + +However, note that this strategy increases write operations to the database, which could affect application performance. + ## Prep steps ### ♨️ Java version diff --git a/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java b/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java index ba61209..9b1be5d 100644 --- a/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java +++ b/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java @@ -13,6 +13,7 @@ import org.bson.codecs.Codec; import org.bson.codecs.DecoderContext; import org.bson.codecs.EncoderContext; import org.bson.types.ObjectId; +import org.jetbrains.annotations.NotNull; public class MoveCodec extends GenericCodec { private final Codec documentCodec; @@ -28,6 +29,8 @@ public class MoveCodec extends GenericCodec { doc.put("_id", new ObjectId(move.getId())); + doc.put("schemaVersion", move.getSchemaVersion()); + doc.put("name", move.getName()); doc.put("category", move.getCategory()); @@ -55,12 +58,52 @@ public class MoveCodec extends GenericCodec { @Override public Move decode(BsonReader reader, DecoderContext decoderContext) { + Document document = documentCodec.decode(reader, decoderContext); + + Integer schemaVersion = document.getInteger("schemaVersion"); + + return switch (schemaVersion) { + case 1 -> decodeV1(document); + case 2 -> decodeV2(document); + default -> throw new IllegalArgumentException("Unsupported schema version: " + schemaVersion); + }; + } + + private @NotNull Move decodeV1(@NotNull Document document) { + Move move = new Move(); + + move.setId(document.getObjectId("_id") + .toString()); + + move.setSchemaVersion(document.getInteger("schemaVersion")); + + move.setName(document.getString("name")); + + move.setCategory(MoveCategoryName.valueOf(document.getString("category"))); + + move.setPower(document.getInteger("power")); + + move.setAccuracy(document.getInteger("accuracy")); + + Document typeDoc = (Document) document.get("type"); + + move.setType(TypeCodecUtil.extractType(typeDoc)); + + // Read and discard the old pp field + Integer pp = document.getInteger("pp"); + + return move; + } + + private @NotNull Move decodeV2(@NotNull Document document) { Move move = new Move(); move.setId(document.getObjectId("_id") .toString()); + move.setSchemaVersion(document.getInteger("schemaVersion")); + move.setName(document.getString("name")); move.setCategory(MoveCategoryName.valueOf(document.getString("category"))); @@ -75,4 +118,5 @@ public class MoveCodec extends GenericCodec { return move; } + } diff --git a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java index a4bdd36..c2137b5 100644 --- a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java +++ b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java @@ -36,6 +36,8 @@ public class PokemongCodec extends GenericCodec { doc.put("_id", new ObjectId(pokemong.getId())); + doc.put("schemaVersion", pokemong.getSchemaVersion()); + doc.put("nickname", pokemong.getNickname()); doc.put("dob", @@ -103,11 +105,23 @@ public class PokemongCodec extends GenericCodec { @Override public Pokemong decode(BsonReader reader, DecoderContext decoderContext) { Document document = documentCodec.decode(reader, decoderContext); + + Integer schemaVersion = document.getInteger("schemaVersion"); + + return switch (schemaVersion) { + case 1 -> decodeV1(document); + default -> throw new IllegalArgumentException("Unsupported schema version: " + schemaVersion); + }; + } + + private @NotNull Pokemong decodeV1(@NotNull Document document) { Pokemong pokemong = new Pokemong(); pokemong.setId(document.getObjectId("_id") .toString()); + pokemong.setSchemaVersion(document.getInteger("schemaVersion")); + pokemong.setNickname(document.getString("nickname")); Date dob = document.getDate("dob"); @@ -134,10 +148,10 @@ public class PokemongCodec extends GenericCodec { pokemong.setTrainer(trainerId.toString()); } - List types = document.getList("types", Document.class) - .stream() - .map(TypeCodecUtil::extractType) - .collect(Collectors.toList()); + Set types = document.getList("types", Document.class) + .stream() + .map(TypeCodecUtil::extractType) + .collect(Collectors.toSet()); pokemong.setTypes(types); Set moveSet = document.getList("moveSet", Document.class) diff --git a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java index d37e723..0f5a7d7 100644 --- a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java +++ b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java @@ -18,6 +18,7 @@ import java.time.LocalDate; import java.time.ZoneId; import java.util.Date; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; public class TrainerCodec extends GenericCodec { @@ -34,6 +35,8 @@ public class TrainerCodec extends GenericCodec { doc.put("_id", new ObjectId(trainer.getId())); + doc.put("schemaVersion", trainer.getSchemaVersion()); + doc.put("name", trainer.getName()); LocalDate dob = trainer.getDob(); @@ -77,11 +80,24 @@ public class TrainerCodec extends GenericCodec { @Override public Trainer decode(BsonReader reader, DecoderContext decoderContext) { Document document = documentCodec.decode(reader, decoderContext); + + Integer schemaVersion = document.getInteger("schemaVersion"); + + return switch (schemaVersion) { + case 1 -> decodeV1(document); + default -> throw new IllegalArgumentException("Unsupported schema version: " + schemaVersion); + }; + } + + @NotNull + private static Trainer decodeV1(Document document) { Trainer trainer = new Trainer(); trainer.setId(document.getObjectId("_id") .toString()); + trainer.setSchemaVersion(document.getInteger("schemaVersion")); + trainer.setName(document.getString("name")); Date dob = document.getDate("dob"); @@ -101,7 +117,7 @@ public class TrainerCodec extends GenericCodec { .collect(Collectors.toList()); trainer.setPastOpponents(pastOpponentsIds); - List pokemongList = document + Set pokemongList = document .getList("pokemongs", Document.class) .stream() .map(pokemongDoc -> { @@ -111,7 +127,7 @@ public class TrainerCodec extends GenericCodec { pokemong.setSpecies(PokemongName.valueOf(pokemongDoc.getString("species"))); return pokemong; }) - .collect(Collectors.toList()); + .collect(Collectors.toSet()); trainer.setPokemongs(pokemongList); return trainer; } diff --git a/src/main/java/fr/uca/iut/controllers/GenericController.java b/src/main/java/fr/uca/iut/controllers/GenericController.java index 70b30d2..ddd16ec 100644 --- a/src/main/java/fr/uca/iut/controllers/GenericController.java +++ b/src/main/java/fr/uca/iut/controllers/GenericController.java @@ -47,7 +47,6 @@ public abstract class GenericController { public Response createOne(T entity) { try { - service.validateOne(entity); T newEntity = service.addOne(entity); return Response.status(Response.Status.CREATED) @@ -66,7 +65,6 @@ public abstract class GenericController { @Consumes(MediaType.APPLICATION_JSON) public Response updateOne(@PathParam("id") String id, T entity) { try { - service.validateOne(entity); entity.setId(id); T updatedEntity = service.updateOne(entity); diff --git a/src/main/java/fr/uca/iut/entities/GenericVersionedEntity.java b/src/main/java/fr/uca/iut/entities/GenericVersionedEntity.java new file mode 100644 index 0000000..7ede19a --- /dev/null +++ b/src/main/java/fr/uca/iut/entities/GenericVersionedEntity.java @@ -0,0 +1,21 @@ +package fr.uca.iut.entities; + +/** + * The strategy for incrementing the schema version number is simple. + *

+ * `schemaVersion` will have to start at 1, and need to be incremented by one at each schema change. + *

+ * Every change to the schema needs to involve the schema version number being incremented. + */ +public abstract class GenericVersionedEntity extends GenericEntity { + + private Integer schemaVersion; + + public Integer getSchemaVersion() { + return schemaVersion; + } + + public void setSchemaVersion(Integer schemaVersion) { + this.schemaVersion = schemaVersion; + } +} diff --git a/src/main/java/fr/uca/iut/entities/Move.java b/src/main/java/fr/uca/iut/entities/Move.java index b25cd45..050396c 100644 --- a/src/main/java/fr/uca/iut/entities/Move.java +++ b/src/main/java/fr/uca/iut/entities/Move.java @@ -3,8 +3,9 @@ package fr.uca.iut.entities; import fr.uca.iut.entities.embedded.Type; import fr.uca.iut.utils.enums.MoveCategoryName; -public class Move extends GenericEntity { +public class Move extends GenericVersionedEntity { public static final String COLLECTION_NAME = "moves"; + public static final Integer LATEST_SCHEMA_VERSION = 2; private String name; private MoveCategoryName category; diff --git a/src/main/java/fr/uca/iut/entities/Pokemong.java b/src/main/java/fr/uca/iut/entities/Pokemong.java index 3645a6e..2c30cd8 100644 --- a/src/main/java/fr/uca/iut/entities/Pokemong.java +++ b/src/main/java/fr/uca/iut/entities/Pokemong.java @@ -10,9 +10,11 @@ import java.util.Collections; import java.util.List; import java.util.Set; -public class Pokemong extends GenericEntity { +public class Pokemong extends GenericVersionedEntity { public static final String COLLECTION_NAME = "pokemongs"; + public static final Integer LATEST_SCHEMA_VERSION = 1; + @Nullable private String nickname; private LocalDate dob; @@ -22,7 +24,7 @@ public class Pokemong extends GenericEntity { private List evoTrack; @Nullable private String trainer; - private List types; + private Set types; /** * pokemong.moveSet: [{_id: ObjectId, name: String}] @@ -73,11 +75,11 @@ public class Pokemong extends GenericEntity { this.trainer = trainer; } - public List getTypes() { - return Collections.unmodifiableList(types); + public Set getTypes() { + return Collections.unmodifiableSet(types); } - public void setTypes(List types) { + public void setTypes(Set types) { this.types = types; } @@ -111,7 +113,7 @@ public class Pokemong extends GenericEntity { } public List getEvoTrack() { - return evoTrack; + return Collections.unmodifiableList(evoTrack); } public Integer getEvoStage() { diff --git a/src/main/java/fr/uca/iut/entities/Trainer.java b/src/main/java/fr/uca/iut/entities/Trainer.java index ea2fef7..a8dc22d 100644 --- a/src/main/java/fr/uca/iut/entities/Trainer.java +++ b/src/main/java/fr/uca/iut/entities/Trainer.java @@ -5,16 +5,18 @@ import fr.uca.iut.entities.denormalized.TrainerPokemong; import java.time.LocalDate; import java.util.Collections; import java.util.List; +import java.util.Set; -public class Trainer extends GenericEntity { +public class Trainer extends GenericVersionedEntity { public static final String COLLECTION_NAME = "trainers"; + public static final Integer LATEST_SCHEMA_VERSION = 1; private String name; private LocalDate dob; private Integer wins; private Integer losses; private List pastOpponents; - private List pokemongs; + private Set pokemongs; public Trainer() {} @@ -58,11 +60,19 @@ public class Trainer extends GenericEntity { this.pastOpponents = pastOpponents; } - public List getPokemongs() { - return Collections.unmodifiableList(pokemongs); + public Set getPokemongs() { + return Collections.unmodifiableSet(pokemongs); } - public void setPokemongs(List pokemongs) { + public void setPokemongs(Set pokemongs) { this.pokemongs = pokemongs; } + + public void addPokemong(TrainerPokemong trainerPokemong) { + pokemongs.add(trainerPokemong); + } + + public void removePokemong(TrainerPokemong trainerPokemong) { + pokemongs.remove(trainerPokemong); + } } diff --git a/src/main/java/fr/uca/iut/entities/embedded/Type.java b/src/main/java/fr/uca/iut/entities/embedded/Type.java index 5af9d0d..452a6bd 100644 --- a/src/main/java/fr/uca/iut/entities/embedded/Type.java +++ b/src/main/java/fr/uca/iut/entities/embedded/Type.java @@ -2,7 +2,6 @@ package fr.uca.iut.entities.embedded; import fr.uca.iut.utils.enums.TypeName; -import java.util.Collections; import java.util.List; import java.util.Objects; @@ -23,7 +22,7 @@ public class Type { } public List getWeakAgainst() { - return Collections.unmodifiableList(weakAgainst); + return weakAgainst; } public void setWeakAgainst(List weakAgainst) { @@ -31,7 +30,7 @@ public class Type { } public List getEffectiveAgainst() { - return Collections.unmodifiableList(effectiveAgainst); + return effectiveAgainst; } public void setEffectiveAgainst(List effectiveAgainst) { diff --git a/src/main/java/fr/uca/iut/services/GenericService.java b/src/main/java/fr/uca/iut/services/GenericService.java index 3bd1435..6f2ab6f 100644 --- a/src/main/java/fr/uca/iut/services/GenericService.java +++ b/src/main/java/fr/uca/iut/services/GenericService.java @@ -17,10 +17,20 @@ public abstract class GenericService { } public T addOne(@NotNull T entity) { + validateOne(entity); repository.persist(entity); return entity; } + /** + * Override me and start with `super.validateOne(entity);` + */ + public void validateOne(T entity) throws NonValidEntityException { + if (entity == null) { + throw new NonValidEntityException("entity was null"); + } + } + @Nullable public T getOneById(String id) { return repository.findById(id); @@ -37,15 +47,12 @@ public abstract class GenericService { } } - @Nullable - public abstract T updateOne(@NotNull T entity); - /** * Override me and start with `super.validateOne(entity);` */ - public void validateOne(T entity) { - if (entity == null) { - throw new NonValidEntityException("entity was null"); - } + @Nullable + public T updateOne(@NotNull T entity) { + validateOne(entity); + return entity; } } diff --git a/src/main/java/fr/uca/iut/services/MoveService.java b/src/main/java/fr/uca/iut/services/MoveService.java index ed30c0e..24f00b4 100644 --- a/src/main/java/fr/uca/iut/services/MoveService.java +++ b/src/main/java/fr/uca/iut/services/MoveService.java @@ -13,6 +13,8 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; @ApplicationScoped public class MoveService extends GenericService { @@ -27,19 +29,70 @@ public class MoveService extends GenericService { setRepository(moveRepository); } + @Override + public void validateOne(Move move) { + + super.validateOne(move); + + List errors = new ArrayList<>(); + + if (StringUtils.isBlankStringOrNull(move.getName())) { + errors.add("move name was null, blank or empty"); + } + + if (move.getPower() == null || move.getPower() < 0) { + errors.add("move power was null or negative"); + } + + if (move.getCategory() == null) { + errors.add("move category was null or invalid"); + } + + if (move.getAccuracy() == null || move.getAccuracy() < 0) { + errors.add("move accuracy was null or negative"); + } + + if (move.getType() == null) { + errors.add("move type was null or invalid"); + } + + if (move.getSchemaVersion() == null || !Objects.equals(move.getSchemaVersion(), Move.LATEST_SCHEMA_VERSION)) { + errors.add("move schema version was null or not the latest version: " + Move.LATEST_SCHEMA_VERSION); + } + + if (!errors.isEmpty()) { + throw new NonValidEntityException("Validation errors: " + String.join(", ", errors)); + } + } + + @Nullable + @Override + public Move getOneById(String id) { + return migrateToV2(super.getOneById(id)); + } + + @Override + public List getAll() { + return super.getAll() + .stream() + .map(this::migrateToV2) + .collect(Collectors.toList()); + } + @Override public void deleteOneById(String id) { - super.deleteOneById(id); List pokemongs = pokemongService.findByMove(id); for (Pokemong pokemong : pokemongs) { pokemong.removeMove(id); pokemongService.updateOne(pokemong); } + super.deleteOneById(id); } @Override @Nullable public Move updateOne(@NotNull Move move) { + super.updateOne(move); Move existingMove = moveRepository.findById(move.getId()); if (existingMove != null) { if (!existingMove.getName() @@ -62,36 +115,20 @@ public class MoveService extends GenericService { return existingMove; } - @Override - public void validateOne(Move move) { - - super.validateOne(move); - - List errors = new ArrayList<>(); - - if (StringUtils.isBlankStringOrNull(move.getName())) { - errors.add("move name was null, blank or empty"); - } - - if (move.getPower() == null || move.getPower() < 0) { - errors.add("move power was null or negative"); - } - - if (move.getCategory() == null) { - errors.add("move category was null or invalid"); - } - - if (move.getAccuracy() == null || move.getAccuracy() < 0) { - errors.add("move accuracy was null or negative"); - } - - if (move.getType() == null) { - errors.add("move type was null or invalid"); - } - - if (!errors.isEmpty()) { - throw new NonValidEntityException("Validation errors: " + String.join(", ", errors)); + /** + * We want to migrate the documents incrementally, so we upgrade the + * schema version if it is less than the current schema version, + * and then save the updated document back to the database. + * + * @param move the Move found by the repository + * @return the Move(V2) based on the Move from the repository + */ + private Move migrateToV2(Move move) { + if (move != null && move.getSchemaVersion() < 2) { + move.setSchemaVersion(2); + moveRepository.persistOrUpdate(move); } + return move; } public boolean existsById(String moveId) { diff --git a/src/main/java/fr/uca/iut/services/PokemongService.java b/src/main/java/fr/uca/iut/services/PokemongService.java index c80886c..a047ef4 100644 --- a/src/main/java/fr/uca/iut/services/PokemongService.java +++ b/src/main/java/fr/uca/iut/services/PokemongService.java @@ -1,7 +1,8 @@ package fr.uca.iut.services; import com.mongodb.lang.Nullable; -import fr.uca.iut.entities.*; +import fr.uca.iut.entities.Pokemong; +import fr.uca.iut.entities.Trainer; import fr.uca.iut.entities.denormalized.PokemongMove; import fr.uca.iut.entities.denormalized.TrainerPokemong; import fr.uca.iut.entities.embedded.Type; @@ -39,81 +40,19 @@ public class PokemongService extends GenericService { @Override public Pokemong addOne(@NotNull Pokemong pokemong) { Pokemong persistedPokemong = super.addOne(pokemong); - - Trainer trainer = trainerService.getOneById(pokemong.getTrainer()); - if (trainer != null) { - TrainerPokemong trainerPokemong = new TrainerPokemong(); - trainerPokemong.setId(pokemong.getId()); - trainerPokemong.setNickname(pokemong.getNickname()); - trainerPokemong.setSpecies(pokemong.getSpecies()); - trainer.getPokemongs() - .add(trainerPokemong); - trainerService.updateOne(trainer); - } - return persistedPokemong; - } - - @Override - public void deleteOneById(String id) { - Pokemong pokemong = getOneById(id); - if (pokemong != null && pokemong.getTrainer() != null) { + String trainerId = pokemong.getTrainer(); + if (trainerId != null) { Trainer trainer = trainerService.getOneById(pokemong.getTrainer()); if (trainer != null) { - trainer.getPokemongs() - .removeIf(trainerPokemong -> trainerPokemong.getId() - .equals(id)); + TrainerPokemong trainerPokemong = new TrainerPokemong(); + trainerPokemong.setId(pokemong.getId()); + trainerPokemong.setNickname(pokemong.getNickname()); + trainerPokemong.setSpecies(pokemong.getSpecies()); + trainer.addPokemong(trainerPokemong); trainerService.updateOne(trainer); } } - super.deleteOneById(id); - } - - @Override - @Nullable - public Pokemong updateOne(@NotNull Pokemong pokemong) { - Pokemong existingPokemong = pokemongRepository.findById(pokemong.getId()); - if (existingPokemong != null) { - boolean nicknameChanged = !Objects.equals(existingPokemong.getNickname(), pokemong.getNickname()); - boolean evoStageChanged = !Objects.equals(existingPokemong.getEvoStage(), pokemong.getEvoStage()); - boolean evoTrackChanged = !Objects.equals(existingPokemong.getEvoTrack(), pokemong.getEvoTrack()); - - existingPokemong.setNickname(pokemong.getNickname()); - existingPokemong.setDob(pokemong.getDob()); - existingPokemong.setLevel(pokemong.getLevel()); - existingPokemong.setPokedexId(pokemong.getPokedexId()); - existingPokemong.setEvoStage(pokemong.getEvoStage()); - existingPokemong.setEvoTrack(pokemong.getEvoTrack()); - existingPokemong.setTrainer(pokemong.getTrainer()); - existingPokemong.setTypes(pokemong.getTypes()); - existingPokemong.setMoveSet(pokemong.getMoveSet()); - - pokemongRepository.persistOrUpdate(existingPokemong); - - if (nicknameChanged || evoStageChanged || evoTrackChanged) { - Trainer trainer = trainerService.getOneById(existingPokemong.getTrainer()); - if (trainer != null) { - TrainerPokemong trainerPokemong = trainer.getPokemongs() - .stream() - .filter(tp -> tp.getId() - .equals(existingPokemong.getId())) - .findFirst() - .orElse(null); - - if (trainerPokemong != null) { - if (nicknameChanged) { - trainerPokemong.setNickname(existingPokemong.getNickname()); - } - - if (evoStageChanged || evoTrackChanged) { - trainerPokemong.setSpecies(existingPokemong.getSpecies()); - } - - trainerService.updateOne(trainer); - } - } - } - } - return existingPokemong; + return persistedPokemong; } @Override @@ -143,7 +82,7 @@ public class PokemongService extends GenericService { errors.add("pokemong evo track was null or invalid"); } - List types = pokemong.getTypes(); + Set types = pokemong.getTypes(); if (types == null || types.size() == 0 || types.size() > 2) @@ -174,11 +113,82 @@ public class PokemongService extends GenericService { } } + if (pokemong.getSchemaVersion() == null || + !Objects.equals(pokemong.getSchemaVersion(), Pokemong.LATEST_SCHEMA_VERSION)) + { + errors.add( + "pokemong schema version was null or not the latest version: " + Pokemong.LATEST_SCHEMA_VERSION); + } + if (!errors.isEmpty()) { throw new NonValidEntityException("Validation errors: " + String.join(", ", errors)); } } + @Override + public void deleteOneById(String id) { + Pokemong pokemong = getOneById(id); + if (pokemong != null && pokemong.getTrainer() != null) { + Trainer trainer = trainerService.getOneById(pokemong.getTrainer()); + if (trainer != null) { + trainer.getPokemongs() + .removeIf(trainerPokemong -> trainerPokemong.getId() + .equals(id)); + trainerService.updateOne(trainer); + } + } + super.deleteOneById(id); + } + + @Override + @Nullable + public Pokemong updateOne(@NotNull Pokemong pokemong) { + super.updateOne(pokemong); + Pokemong existingPokemong = pokemongRepository.findById(pokemong.getId()); + if (existingPokemong != null) { + boolean nicknameChanged = !Objects.equals(existingPokemong.getNickname(), pokemong.getNickname()); + boolean evoStageChanged = !Objects.equals(existingPokemong.getEvoStage(), pokemong.getEvoStage()); + boolean evoTrackChanged = !Objects.equals(existingPokemong.getEvoTrack(), pokemong.getEvoTrack()); + + existingPokemong.setNickname(pokemong.getNickname()); + existingPokemong.setDob(pokemong.getDob()); + existingPokemong.setLevel(pokemong.getLevel()); + existingPokemong.setPokedexId(pokemong.getPokedexId()); + existingPokemong.setEvoStage(pokemong.getEvoStage()); + existingPokemong.setEvoTrack(pokemong.getEvoTrack()); + existingPokemong.setTrainer(pokemong.getTrainer()); + existingPokemong.setTypes(pokemong.getTypes()); + existingPokemong.setMoveSet(pokemong.getMoveSet()); + + pokemongRepository.persistOrUpdate(existingPokemong); + + if (nicknameChanged || evoStageChanged || evoTrackChanged) { + Trainer trainer = trainerService.getOneById(existingPokemong.getTrainer()); + if (trainer != null) { + TrainerPokemong trainerPokemong = trainer.getPokemongs() + .stream() + .filter(tp -> tp.getId() + .equals(existingPokemong.getId())) + .findFirst() + .orElse(null); + + if (trainerPokemong != null) { + if (nicknameChanged) { + trainerPokemong.setNickname(existingPokemong.getNickname()); + } + + if (evoStageChanged || evoTrackChanged) { + trainerPokemong.setSpecies(existingPokemong.getSpecies()); + } + + trainerService.updateOne(trainer); + } + } + } + } + return existingPokemong; + } + public List findByMove(String id) { return pokemongRepository.findByMove(id); } @@ -193,10 +203,12 @@ public class PokemongService extends GenericService { return repository.existsById(pokemongId); } - public void batchUpdatePokemongTrainers(@NotNull List trainerPokemongs, @Nullable String trainerId) { + public void batchUpdatePokemongTrainers(@NotNull Set trainerPokemongs, + @Nullable String trainerId) + { for (TrainerPokemong trainerPokemong : trainerPokemongs) { Pokemong pokemong = getOneById(trainerPokemong.getId()); - if (pokemong != null) { + if (pokemong != null && !Objects.equals(pokemong.getTrainer(), trainerId)) { pokemong.setTrainer(trainerId); updateOne(pokemong); } diff --git a/src/main/java/fr/uca/iut/services/TrainerService.java b/src/main/java/fr/uca/iut/services/TrainerService.java index 5817a99..82ccc30 100644 --- a/src/main/java/fr/uca/iut/services/TrainerService.java +++ b/src/main/java/fr/uca/iut/services/TrainerService.java @@ -12,8 +12,8 @@ import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; @ApplicationScoped public class TrainerService extends GenericService { @@ -33,38 +33,14 @@ public class TrainerService extends GenericService { public Trainer addOne(@NotNull Trainer trainer) { Trainer persistedTrainer = super.addOne(trainer); + // If this trainer gained pokemongs, that pokemong's ex-trainer if any needs to lose said pokemong + transferNewlyArrivedTrainerPokemongs(new HashSet<>(), persistedTrainer.getPokemongs()); + // all owned pokemongs gain this trainer's reference pokemongService.batchUpdatePokemongTrainers(trainer.getPokemongs(), trainer.getId()); return persistedTrainer; } - @Override - public void deleteOneById(String id) { - Trainer trainer = getOneById(id); - - if (trainer != null) { - pokemongService.batchUpdatePokemongTrainers(trainer.getPokemongs(), null); - } - - super.deleteOneById(id); - } - - @Nullable - @Override - public Trainer updateOne(@NotNull Trainer trainer) { - Trainer existingTrainer = trainerRepository.findById(trainer.getId()); - if (existingTrainer != null) { - existingTrainer.setName(trainer.getName()); - existingTrainer.setDob(trainer.getDob()); - existingTrainer.setWins(trainer.getLosses()); - existingTrainer.setLosses(trainer.getLosses()); - existingTrainer.setPastOpponents(trainer.getPastOpponents()); - existingTrainer.setPokemongs(trainer.getPokemongs()); - trainerRepository.persistOrUpdate(existingTrainer); - } - return existingTrainer; - } - @Override public void validateOne(Trainer trainer) { @@ -95,13 +71,13 @@ public class TrainerService extends GenericService { } else { for (String trainerId : pastOpponents) { - if (StringUtils.isBlankStringOrNull(trainerId) || !trainerRepository.existsById(trainerId)) { - errors.add("trainer past opponents collection contained an invalid or unknown id"); + if (StringUtils.isBlankStringOrNull(trainerId)) { + errors.add("trainer past opponents collection contained an invalid id: " + trainerId); } } } - List pokemongs = trainer.getPokemongs(); + Set pokemongs = trainer.getPokemongs(); if (pokemongs == null) { errors.add("trainer pokemongs collection was null or invalid"); @@ -129,8 +105,84 @@ public class TrainerService extends GenericService { } } + if (trainer.getSchemaVersion() == null || + !Objects.equals(trainer.getSchemaVersion(), Trainer.LATEST_SCHEMA_VERSION)) + { + errors.add("trainer schema version was null or not the latest version: " + Trainer.LATEST_SCHEMA_VERSION); + } + if (!errors.isEmpty()) { throw new NonValidEntityException("Validation errors: " + String.join(", ", errors)); } } + + @Override + public void deleteOneById(String id) { + Trainer trainer = getOneById(id); + + if (trainer != null) { + pokemongService.batchUpdatePokemongTrainers(trainer.getPokemongs(), null); + } + + super.deleteOneById(id); + } + + @Nullable + @Override + public Trainer updateOne(@NotNull Trainer trainer) { + super.updateOne(trainer); + Trainer existingTrainer = trainerRepository.findById(trainer.getId()); + if (existingTrainer != null) { + Set oldPokemongs = existingTrainer.getPokemongs(); + + existingTrainer.setName(trainer.getName()); + existingTrainer.setDob(trainer.getDob()); + existingTrainer.setWins(trainer.getLosses()); + existingTrainer.setLosses(trainer.getLosses()); + existingTrainer.setPastOpponents(trainer.getPastOpponents()); + existingTrainer.setPokemongs(trainer.getPokemongs()); + trainerRepository.persistOrUpdate(existingTrainer); + + Set newPokemongs = trainer.getPokemongs(); + + // all old pokemongs who are not there anymore lose their trainer reference + pokemongService.batchUpdatePokemongTrainers( + oldPokemongs.stream() + .filter(tp -> !newPokemongs.contains(tp)) + .collect(Collectors.toSet()), + null); + // If this trainer gained a pokemong, that pokemong's ex-trainer if any needs to lose said pokemong + transferNewlyArrivedTrainerPokemongs(oldPokemongs, newPokemongs); + // all new pokemongs who were not there before gain this trainer's reference + pokemongService.batchUpdatePokemongTrainers( + newPokemongs.stream() + .filter(tp -> !oldPokemongs.contains(tp)) + .collect(Collectors.toSet()), + existingTrainer.getId()); + } + return existingTrainer; + } + + private void transferNewlyArrivedTrainerPokemongs( + @NotNull Set oldPokemongs, + @NotNull Set newPokemongs + ) + { + for (TrainerPokemong tp : newPokemongs) { + if (oldPokemongs.isEmpty() || !oldPokemongs.contains(tp)) { + Pokemong pokemong = pokemongService.getOneById(tp.getId()); + if (pokemong != null) { + String oldTrainerId = pokemong.getTrainer(); + // If the pokemong already had a trainer, remove it from the old trainer's pokemongs list + if (oldTrainerId != null) { + Trainer oldTrainer = getOneById(oldTrainerId); + if (oldTrainer != null) { + oldTrainer.removePokemong(tp); + updateOne(oldTrainer); + } + } + } + } + } + } } diff --git a/src/main/resources/META-INF/openapi.yaml b/src/main/resources/META-INF/openapi.yaml index b3192f0..86aea97 100644 --- a/src/main/resources/META-INF/openapi.yaml +++ b/src/main/resources/META-INF/openapi.yaml @@ -310,6 +310,7 @@ components: - evoTrack - types - moveSet + - schemaVersion properties: nickname: type: string @@ -347,6 +348,8 @@ components: maxItems: 4 items: $ref: '#/components/schemas/PokemongMove' + schemaVersion: + $ref: '#/components/schemas/SchemaVersion' Move: type: object @@ -356,6 +359,7 @@ components: - category - accuracy - type + - schemaVersion properties: name: type: string @@ -370,6 +374,8 @@ components: minimum: 0 type: $ref: '#/components/schemas/TypeName' + schemaVersion: + $ref: '#/components/schemas/SchemaVersion' Trainer: type: object @@ -380,6 +386,7 @@ components: - losses - pastOpponents - pokemongs + - schemaVersion properties: name: type: string @@ -402,6 +409,8 @@ components: type: array items: $ref: '#/components/schemas/TrainerPokemong' + schemaVersion: + $ref: '#/components/schemas/SchemaVersion' PokemongMove: type: object @@ -431,6 +440,11 @@ components: species: $ref: '#/components/schemas/PokemongName' + SchemaVersion: + type: integer + minimum: 1 + description: must be >= 1, and furthermore must be >= latest schema version + MoveCategoryName: type: string enum: [ -- 2.36.3 From 05e15ba5bac91145aea46530e640e5d3ec471470 Mon Sep 17 00:00:00 2001 From: "alexis.drai@etu.uca.fr" Date: Wed, 31 May 2023 20:46:42 +0200 Subject: [PATCH 4/6] :bento: Create a sample dataset and a postman collection --- docs/postman_collection.json | 384 +++++++++++++++++++++++++++++ docs/sample-dataset/moves.json | 40 +++ docs/sample-dataset/pokemongs.json | 98 ++++++++ docs/sample-dataset/trainers.json | 56 +++++ 4 files changed, 578 insertions(+) create mode 100644 docs/postman_collection.json create mode 100644 docs/sample-dataset/moves.json create mode 100644 docs/sample-dataset/pokemongs.json create mode 100644 docs/sample-dataset/trainers.json diff --git a/docs/postman_collection.json b/docs/postman_collection.json new file mode 100644 index 0000000..baad12a --- /dev/null +++ b/docs/postman_collection.json @@ -0,0 +1,384 @@ +{ + "info": { + "_postman_id": "11aa7a76-83a4-4b92-940f-528e29b66df8", + "name": "PoKeMoNg", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "25802734" + }, + "item": [ + { + "name": "move", + "item": [ + { + "name": "Create 1 move", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"Bubble beam\",\r\n \"category\": \"PHYSICAL\",\r\n \"power\": 10,\r\n \"accuracy\": 85,\r\n \"type\": \r\n {\r\n \"name\": \"WATER\",\r\n \"weakAgainst\": [\"GRASS\"],\r\n \"effectiveAgainst\": [\"FIRE\", \"GROUND\"]\r\n },\r\n \"schemaVersion\": 2\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/move", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "move" + ] + } + }, + "response": [] + }, + { + "name": "Get all moves", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/move", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "move" + ] + } + }, + "response": [] + }, + { + "name": "Get 1 move", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/move/60a64f7eae945a6e60b0e917", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "move", + "60a64f7eae945a6e60b0e917" + ] + } + }, + "response": [] + }, + { + "name": "Update 1 move", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"schemaVersion\": 2,\r\n \"name\": \"Ember UPDATED\",\r\n \"category\": \"SPECIAL\",\r\n \"power\": 40,\r\n \"accuracy\": 100,\r\n \"type\": {\r\n \"name\": \"FIRE\",\r\n \"weakAgainst\": [\r\n \"WATER\",\r\n \"GROUND\"\r\n ],\r\n \"effectiveAgainst\": [\r\n \"GRASS\"\r\n ]\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/move/60a64f7eae945a6e60b0e917", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "move", + "60a64f7eae945a6e60b0e917" + ] + } + }, + "response": [] + }, + { + "name": "Delete 1 move", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "http://localhost:8080/move/60a64f7eae945a6e60b0e913", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "move", + "60a64f7eae945a6e60b0e913" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "pokemong", + "item": [ + { + "name": "Create 1 pkmn", + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "content-type": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\r\n \"nickname\": \"Now with moves\",\r\n \"dob\": \"2023-05-07\",\r\n \"level\": 1,\r\n \"pokedexId\": 172,\r\n \"evoStage\": 1,\r\n \"evoTrack\": [\"PICHU\", \"PIKACHU\", \"RAICHU\"],\r\n \"types\": [\r\n {\r\n \"name\": \"ELECTRIC\",\r\n \"weakAgainst\": [\"GROUND\", \"ROCK\"],\r\n \"effectiveAgainst\": [\"WATER\", \"FLYING\"]\r\n }\r\n ],\r\n \"moveSet\": \r\n [\r\n {\r\n \"id\": \"60a64f7eae945a6e60b0e917\",\r\n \"name\": \"Ember\"\r\n }\r\n ],\r\n \"schemaVersion\": 1\r\n}" + }, + "url": { + "raw": "http://localhost:8080/pokemong", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "pokemong" + ] + } + }, + "response": [] + }, + { + "name": "Get all pkmn", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/pokemong", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "pokemong" + ] + } + }, + "response": [] + }, + { + "name": "Get 1 pkmn", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/pokemong/60a64f7eae945a6e60b0e911", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "pokemong", + "60a64f7eae945a6e60b0e911" + ] + } + }, + "response": [] + }, + { + "name": "Update 1 pkmn", + "request": { + "method": "PUT", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"schemaVersion\": 1,\n \"nickname\": \"Sparky UPDATED\",\n \"dob\": \"1994-02-18\",\n \"level\": 15,\n \"pokedexId\": 1,\n \"evoStage\": 1,\n \"evoTrack\": [\n \"BULBASAUR\",\n \"IVYSAUR\",\n \"VENUSAUR\"\n ],\n \"trainer\": \"60a64f7eae945a6e60b0e914\",\n \"types\": [\n {\n \"name\": \"GRASS\",\n \"weakAgainst\": [\n \"FIRE\"\n ],\n \"effectiveAgainst\": [\n \"WATER\",\n \"GROUND\"\n ]\n }\n ],\n \"moveSet\": [\n {\n \"id\": \"60a64f7eae945a6e60b0e912\",\n \"name\": \"Vine Whip\"\n }\n ]\n}" + }, + "url": { + "raw": "http://localhost:8080/pokemong/60a64f7eae945a6e60b0e911", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "pokemong", + "60a64f7eae945a6e60b0e911" + ] + } + }, + "response": [] + }, + { + "name": "Delete 1 pkmn", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "http://localhost:8080/pokemong/60a64f7eae945a6e60b0e911", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "pokemong", + "60a64f7eae945a6e60b0e911" + ] + } + }, + "response": [] + } + ] + }, + { + "name": "trainer", + "item": [ + { + "name": "Create 1 trainer", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"schemaVersion\": 1,\r\n \"name\": \"Bloop\",\r\n \"dob\": \"1997-02-18\",\r\n \"wins\": 1,\r\n \"losses\": 50,\r\n \"pastOpponents\": [\r\n \"60a64f7eae945a6e60b0e915\"\r\n ],\r\n \"pokemongs\": [\r\n {\r\n \"id\": \"60a64f7eae945a6e60b0e911\",\r\n \"nickname\": \"Sparky UPDATED\",\r\n \"species\": \"IVYSAUR\"\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/trainer", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "trainer" + ] + } + }, + "response": [] + }, + { + "name": "Get all trainers", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/trainer", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "trainer" + ] + } + }, + "response": [] + }, + { + "name": "Get 1 trainer", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/trainer/60a64f7eae945a6e60b0e914", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "trainer", + "60a64f7eae945a6e60b0e914" + ] + } + }, + "response": [] + }, + { + "name": "Update 1 trainer", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"schemaVersion\": 1,\r\n \"name\": \"Brock\",\r\n \"dob\": \"1994-02-18\",\r\n \"wins\": 60,\r\n \"losses\": 60,\r\n \"pastOpponents\": [\r\n \"60a64f7eae945a6e60b0e914\"\r\n ],\r\n \"pokemongs\": [\r\n {\r\n \"id\": \"60a64f7eae945a6e60b0e911\",\r\n \"nickname\": \"Sparky UPDATED\",\r\n \"species\": \"IVYSAUR\"\r\n }\r\n ]\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/trainer/60a64f7eae945a6e60b0e915", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "trainer", + "60a64f7eae945a6e60b0e915" + ] + } + }, + "response": [] + }, + { + "name": "Delete 1 trainer", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "http://localhost:8080/trainer/60a64f7eae945a6e60b0e914", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "trainer", + "60a64f7eae945a6e60b0e914" + ] + } + }, + "response": [] + } + ] + } + ] +} \ No newline at end of file diff --git a/docs/sample-dataset/moves.json b/docs/sample-dataset/moves.json new file mode 100644 index 0000000..0bfac4f --- /dev/null +++ b/docs/sample-dataset/moves.json @@ -0,0 +1,40 @@ +{ + "_id": { "$oid":"60a64f7eae945a6e60b0e912" }, + "schemaVersion": 1, + "name": "Vine Whip", + "power": 45, + "pp": 15, + "category": "PHYSICAL", + "accuracy": 100, + "type": { + "name": "GRASS", + "weakAgainst": ["FIRE"], + "effectiveAgainst": ["WATER", "GROUND"] + } +} +{ + "_id": { "$oid":"60a64f7eae945a6e60b0e913" }, + "schemaVersion": 2, + "name": "Tackle", + "power": 40, + "category": "PHYSICAL", + "accuracy": 100, + "type": { + "name": "NORMAL", + "weakAgainst": ["ROCK"], + "effectiveAgainst": [] + } +} +{ + "_id": { "$oid":"60a64f7eae945a6e60b0e917" }, + "schemaVersion": 2, + "name": "Ember", + "power": 40, + "category": "SPECIAL", + "accuracy": 100, + "type": { + "name": "FIRE", + "weakAgainst": ["WATER", "GROUND"], + "effectiveAgainst": ["GRASS"] + } +} diff --git a/docs/sample-dataset/pokemongs.json b/docs/sample-dataset/pokemongs.json new file mode 100644 index 0000000..7ba88b9 --- /dev/null +++ b/docs/sample-dataset/pokemongs.json @@ -0,0 +1,98 @@ +{ + "_id": { + "$oid": "60a64f7eae945a6e60b0e911" + }, + "schemaVersion": 1, + "nickname": "Sparky", + "dob": { + "$date": { + "$numberLong": "761597551000" + } + }, + "level": 15, + "pokedexId": 1, + "evoStage": 1, + "evoTrack": [ + "BULBASAUR", + "IVYSAUR", + "VENUSAUR" + ], + "trainer": { + "$oid": "60a64f7eae945a6e60b0e914" + }, + "types": [ + { + "name": "GRASS", + "weakAgainst": [ + "FIRE" + ], + "effectiveAgainst": [ + "WATER", + "GROUND" + ] + } + ], + "moveSet": [ + { + "_id": { + "$oid": "60a64f7eae945a6e60b0e913" + }, + "name": "Tackle" + }, + { + "_id": { + "$oid": "60a64f7eae945a6e60b0e912" + }, + "name": "Vine Whip" + } + ] +} +{ + "_id": { + "$oid": "60a64f7eae945a6e60b0e916" + }, + "schemaVersion": 1, + "nickname": "Blazey", + "dob": { + "$date": { + "$numberLong": "761597651000" + } + }, + "level": 10, + "pokedexId": 4, + "evoStage": 0, + "evoTrack": [ + "CHARMANDER", + "CHARMELEON", + "CHARIZARD" + ], + "trainer": { + "$oid": "60a64f7eae945a6e60b0e915" + }, + "types": [ + { + "name": "FIRE", + "weakAgainst": [ + "WATER", + "GROUND" + ], + "effectiveAgainst": [ + "GRASS" + ] + } + ], + "moveSet": [ + { + "_id": { + "$oid": "60a64f7eae945a6e60b0e913" + }, + "name": "Tackle" + }, + { + "_id": { + "$oid": "60a64f7eae945a6e60b0e917" + }, + "name": "Ember" + } + ] +} diff --git a/docs/sample-dataset/trainers.json b/docs/sample-dataset/trainers.json new file mode 100644 index 0000000..913b61b --- /dev/null +++ b/docs/sample-dataset/trainers.json @@ -0,0 +1,56 @@ +{ + "_id": { + "$oid": "60a64f7eae945a6e60b0e914" + }, + "schemaVersion": 1, + "name": "Ash", + "dob": { + "$date": { + "$numberLong": "761598551000" + } + }, + "wins": 100, + "losses": 50, + "pastOpponents": [ + { + "$oid": "60a64f7eae945a6e60b0e915" + } + ], + "pokemongs": [ + { + "_id": { + "$oid": "60a64f7eae945a6e60b0e911" + }, + "nickname": "Sparky", + "species": "IVYSAUR" + } + ] +} +{ + "_id": { + "$oid": "60a64f7eae945a6e60b0e915" + }, + "schemaVersion": 1, + "name": "Brock", + "dob": { + "$date": { + "$numberLong": "761596551000" + } + }, + "wins": 70, + "losses": 60, + "pastOpponents": [ + { + "$oid": "60a64f7eae945a6e60b0e914" + } + ], + "pokemongs": [ + { + "_id": { + "$oid": "60a64f7eae945a6e60b0e916" + }, + "nickname": "Blazey", + "species": "CHARMANDER" + } + ] +} -- 2.36.3 From 436ebc976b120a6b9dfbbd83428e4fc388c6eb25 Mon Sep 17 00:00:00 2001 From: "alexis.drai@etu.uca.fr" Date: Wed, 31 May 2023 21:06:48 +0200 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=9B=A2=EF=B8=8F=20=F0=9F=9A=A8=20Impl?= =?UTF-8?q?ement=20bulk=20operations,=20lint=20the=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docs/postman_collection.json | 6 +- .../fr/uca/iut/codecs/move/MoveCodec.java | 10 +- .../iut/codecs/pokemong/PokemongCodec.java | 100 +++++++-------- .../uca/iut/codecs/trainer/TrainerCodec.java | 118 +++++++++--------- .../fr/uca/iut/codecs/type/TypeCodecUtil.java | 14 +-- .../iut/controllers/GenericController.java | 50 ++++---- src/main/java/fr/uca/iut/entities/Move.java | 3 +- .../java/fr/uca/iut/entities/Pokemong.java | 14 +-- .../java/fr/uca/iut/entities/Trainer.java | 7 +- .../entities/denormalized/PokemongMove.java | 3 +- .../denormalized/TrainerPokemong.java | 3 +- .../fr/uca/iut/entities/embedded/Type.java | 7 +- .../iut/repositories/GenericRepository.java | 21 +++- .../iut/repositories/PokemongRepository.java | 2 +- .../fr/uca/iut/services/GenericService.java | 11 +- .../java/fr/uca/iut/services/MoveService.java | 30 +++-- .../fr/uca/iut/services/PokemongService.java | 73 ++++++----- .../fr/uca/iut/services/TrainerService.java | 47 ++++--- 19 files changed, 277 insertions(+), 243 deletions(-) diff --git a/.gitignore b/.gitignore index 0c6da4a..019b6de 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,4 @@ gradle-app.setting # Others docs/todos.md /src/test/resources/application.properties +/docs/sample-dataset/load_data.sh diff --git a/docs/postman_collection.json b/docs/postman_collection.json index baad12a..292f8cd 100644 --- a/docs/postman_collection.json +++ b/docs/postman_collection.json @@ -153,7 +153,7 @@ ], "body": { "mode": "raw", - "raw": "{\r\n \"nickname\": \"Now with moves\",\r\n \"dob\": \"2023-05-07\",\r\n \"level\": 1,\r\n \"pokedexId\": 172,\r\n \"evoStage\": 1,\r\n \"evoTrack\": [\"PICHU\", \"PIKACHU\", \"RAICHU\"],\r\n \"types\": [\r\n {\r\n \"name\": \"ELECTRIC\",\r\n \"weakAgainst\": [\"GROUND\", \"ROCK\"],\r\n \"effectiveAgainst\": [\"WATER\", \"FLYING\"]\r\n }\r\n ],\r\n \"moveSet\": \r\n [\r\n {\r\n \"id\": \"60a64f7eae945a6e60b0e917\",\r\n \"name\": \"Ember\"\r\n }\r\n ],\r\n \"schemaVersion\": 1\r\n}" + "raw": "{\r\n \"nickname\": \"Blappity-bloop\",\r\n \"dob\": \"2023-05-07\",\r\n \"level\": 1,\r\n \"pokedexId\": 172,\r\n \"evoStage\": 1,\r\n \"evoTrack\": [\"PICHU\", \"PIKACHU\", \"RAICHU\"],\r\n \"types\": [\r\n {\r\n \"name\": \"ELECTRIC\",\r\n \"weakAgainst\": [\"GROUND\", \"ROCK\"],\r\n \"effectiveAgainst\": [\"WATER\", \"FLYING\"]\r\n }\r\n ],\r\n \"moveSet\": \r\n [\r\n {\r\n \"id\": \"60a64f7eae945a6e60b0e917\",\r\n \"name\": \"Ember\"\r\n }\r\n ],\r\n \"schemaVersion\": 1\r\n}" }, "url": { "raw": "http://localhost:8080/pokemong", @@ -243,7 +243,7 @@ "method": "DELETE", "header": [], "url": { - "raw": "http://localhost:8080/pokemong/60a64f7eae945a6e60b0e911", + "raw": "http://localhost:8080/pokemong/60a64f7eae945a6e60b0e916", "protocol": "http", "host": [ "localhost" @@ -251,7 +251,7 @@ "port": "8080", "path": [ "pokemong", - "60a64f7eae945a6e60b0e911" + "60a64f7eae945a6e60b0e916" ] } }, diff --git a/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java b/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java index 9b1be5d..deb2d6c 100644 --- a/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java +++ b/src/main/java/fr/uca/iut/codecs/move/MoveCodec.java @@ -20,7 +20,7 @@ public class MoveCodec extends GenericCodec { public MoveCodec() { this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() - .get(Document.class); + .get(Document.class); } @Override @@ -42,8 +42,8 @@ public class MoveCodec extends GenericCodec { Type moveType = move.getType(); Document typeDoc = new Document(); typeDoc.put("name", - moveType.getName() - .toString()); + moveType.getName() + .toString()); typeDoc.put("weakAgainst", moveType.getWeakAgainst()); typeDoc.put("effectiveAgainst", moveType.getEffectiveAgainst()); doc.put("type", typeDoc); @@ -74,7 +74,7 @@ public class MoveCodec extends GenericCodec { Move move = new Move(); move.setId(document.getObjectId("_id") - .toString()); + .toString()); move.setSchemaVersion(document.getInteger("schemaVersion")); @@ -100,7 +100,7 @@ public class MoveCodec extends GenericCodec { Move move = new Move(); move.setId(document.getObjectId("_id") - .toString()); + .toString()); move.setSchemaVersion(document.getInteger("schemaVersion")); diff --git a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java index c2137b5..f734503 100644 --- a/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java +++ b/src/main/java/fr/uca/iut/codecs/pokemong/PokemongCodec.java @@ -27,7 +27,7 @@ public class PokemongCodec extends GenericCodec { public PokemongCodec() { this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() - .get(Document.class); + .get(Document.class); } @Override @@ -42,8 +42,8 @@ public class PokemongCodec extends GenericCodec { doc.put("dob", Date.from(pokemong.getDob() - .atStartOfDay(ZoneId.systemDefault()) - .toInstant())); + .atStartOfDay(ZoneId.systemDefault()) + .toInstant())); doc.put("level", pokemong.getLevel()); @@ -52,9 +52,9 @@ public class PokemongCodec extends GenericCodec { doc.put("evoStage", pokemong.getEvoStage()); List evoTrack = pokemong.getEvoTrack() - .stream() - .map(Enum::name) - .collect(Collectors.toList()); + .stream() + .map(Enum::name) + .collect(Collectors.toList()); doc.put("evoTrack", evoTrack); if (pokemong.getTrainer() != null) { @@ -62,36 +62,36 @@ public class PokemongCodec extends GenericCodec { } List types = pokemong.getTypes() - .stream() - .map(type -> { - Document typeDoc = new Document(); - typeDoc.put("name", - type.getName() - .name()); - List weakAgainst = type.getWeakAgainst() - .stream() - .map(Enum::name) - .collect(Collectors.toList()); - typeDoc.put("weakAgainst", weakAgainst); - List effectiveAgainst = type.getEffectiveAgainst() - .stream() - .map(Enum::name) - .collect(Collectors.toList()); - typeDoc.put("effectiveAgainst", effectiveAgainst); - return typeDoc; - }) - .collect(Collectors.toList()); + .stream() + .map(type -> { + Document typeDoc = new Document(); + typeDoc.put("name", + type.getName() + .name()); + List weakAgainst = type.getWeakAgainst() + .stream() + .map(Enum::name) + .collect(Collectors.toList()); + typeDoc.put("weakAgainst", weakAgainst); + List effectiveAgainst = type.getEffectiveAgainst() + .stream() + .map(Enum::name) + .collect(Collectors.toList()); + typeDoc.put("effectiveAgainst", effectiveAgainst); + return typeDoc; + }) + .collect(Collectors.toList()); doc.put("types", types); List moveSetDocs = pokemong.getMoveSet() - .stream() - .map(move -> { - Document moveDoc = new Document(); - moveDoc.put("_id", new ObjectId(move.getId())); - moveDoc.put("name", move.getName()); - return moveDoc; - }) - .collect(Collectors.toList()); + .stream() + .map(move -> { + Document moveDoc = new Document(); + moveDoc.put("_id", new ObjectId(move.getId())); + moveDoc.put("name", move.getName()); + return moveDoc; + }) + .collect(Collectors.toList()); doc.put("moveSet", moveSetDocs); documentCodec.encode(writer, doc, encoderContext); @@ -118,7 +118,7 @@ public class PokemongCodec extends GenericCodec { Pokemong pokemong = new Pokemong(); pokemong.setId(document.getObjectId("_id") - .toString()); + .toString()); pokemong.setSchemaVersion(document.getInteger("schemaVersion")); @@ -127,8 +127,8 @@ public class PokemongCodec extends GenericCodec { Date dob = document.getDate("dob"); if (dob != null) { pokemong.setDob(dob.toInstant() - .atZone(ZoneId.systemDefault()) - .toLocalDate()); + .atZone(ZoneId.systemDefault()) + .toLocalDate()); } pokemong.setLevel(document.getInteger("level")); @@ -138,9 +138,9 @@ public class PokemongCodec extends GenericCodec { pokemong.setEvoStage(document.getInteger("evoStage")); List evoTrack = document.getList("evoTrack", String.class) - .stream() - .map(PokemongName::valueOf) - .collect(Collectors.toList()); + .stream() + .map(PokemongName::valueOf) + .collect(Collectors.toList()); pokemong.setEvoTrack(evoTrack); ObjectId trainerId = document.getObjectId("trainer"); @@ -149,20 +149,20 @@ public class PokemongCodec extends GenericCodec { } Set types = document.getList("types", Document.class) - .stream() - .map(TypeCodecUtil::extractType) - .collect(Collectors.toSet()); + .stream() + .map(TypeCodecUtil::extractType) + .collect(Collectors.toSet()); pokemong.setTypes(types); Set moveSet = document.getList("moveSet", Document.class) - .stream() - .map(pokemongMoveDoc -> { - PokemongMove move = new PokemongMove(); - move.setId(((ObjectId) pokemongMoveDoc.get("_id")).toString()); - move.setName(pokemongMoveDoc.getString("name")); - return move; - }) - .collect(Collectors.toSet()); + .stream() + .map(pokemongMoveDoc -> { + PokemongMove move = new PokemongMove(); + move.setId(((ObjectId) pokemongMoveDoc.get("_id")).toString()); + move.setName(pokemongMoveDoc.getString("name")); + return move; + }) + .collect(Collectors.toSet()); pokemong.setMoveSet(moveSet); return pokemong; diff --git a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java index 0f5a7d7..ccd874b 100644 --- a/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java +++ b/src/main/java/fr/uca/iut/codecs/trainer/TrainerCodec.java @@ -26,7 +26,50 @@ public class TrainerCodec extends GenericCodec { public TrainerCodec() { this.documentCodec = MongoClientSettings.getDefaultCodecRegistry() - .get(Document.class); + .get(Document.class); + } + + @NotNull + private static Trainer decodeV1(Document document) { + Trainer trainer = new Trainer(); + + trainer.setId(document.getObjectId("_id") + .toString()); + + trainer.setSchemaVersion(document.getInteger("schemaVersion")); + + trainer.setName(document.getString("name")); + + Date dob = document.getDate("dob"); + if (dob != null) { + trainer.setDob(dob.toInstant() + .atZone(ZoneId.systemDefault()) + .toLocalDate()); + } + + trainer.setWins(document.getInteger("wins")); + + trainer.setLosses(document.getInteger("losses")); + + List pastOpponentsIds = document.getList("pastOpponents", ObjectId.class) + .stream() + .map(ObjectId::toString) + .collect(Collectors.toList()); + trainer.setPastOpponents(pastOpponentsIds); + + Set pokemongList = document + .getList("pokemongs", Document.class) + .stream() + .map(pokemongDoc -> { + TrainerPokemong pokemong = new TrainerPokemong(); + pokemong.setId(((ObjectId) pokemongDoc.get("_id")).toString()); + pokemong.setNickname(pokemongDoc.getString("nickname")); + pokemong.setSpecies(PokemongName.valueOf(pokemongDoc.getString("species"))); + return pokemong; + }) + .collect(Collectors.toSet()); + trainer.setPokemongs(pokemongList); + return trainer; } @Override @@ -42,7 +85,7 @@ public class TrainerCodec extends GenericCodec { LocalDate dob = trainer.getDob(); if (dob != null) { doc.put("dob", Date.from(dob.atStartOfDay(ZoneId.systemDefault()) - .toInstant())); + .toInstant())); } doc.put("wins", trainer.getWins()); @@ -50,23 +93,23 @@ public class TrainerCodec extends GenericCodec { doc.put("losses", trainer.getLosses()); List pastOpponentsIds = trainer.getPastOpponents() - .stream() - .map(ObjectId::new) - .collect(Collectors.toList()); + .stream() + .map(ObjectId::new) + .collect(Collectors.toList()); doc.put("pastOpponents", pastOpponentsIds); List pokemongListDoc = trainer.getPokemongs() - .stream() - .map(pokemong -> { - Document moveDoc = new Document(); - moveDoc.put("_id", new ObjectId(pokemong.getId())); - moveDoc.put("nickname", pokemong.getNickname()); - moveDoc.put("species", - pokemong.getSpecies() - .name()); - return moveDoc; - }) - .collect(Collectors.toList()); + .stream() + .map(pokemong -> { + Document moveDoc = new Document(); + moveDoc.put("_id", new ObjectId(pokemong.getId())); + moveDoc.put("nickname", pokemong.getNickname()); + moveDoc.put("species", + pokemong.getSpecies() + .name()); + return moveDoc; + }) + .collect(Collectors.toList()); doc.put("pokemongs", pokemongListDoc); documentCodec.encode(writer, doc, encoderContext); @@ -88,47 +131,4 @@ public class TrainerCodec extends GenericCodec { default -> throw new IllegalArgumentException("Unsupported schema version: " + schemaVersion); }; } - - @NotNull - private static Trainer decodeV1(Document document) { - Trainer trainer = new Trainer(); - - trainer.setId(document.getObjectId("_id") - .toString()); - - trainer.setSchemaVersion(document.getInteger("schemaVersion")); - - trainer.setName(document.getString("name")); - - Date dob = document.getDate("dob"); - if (dob != null) { - trainer.setDob(dob.toInstant() - .atZone(ZoneId.systemDefault()) - .toLocalDate()); - } - - trainer.setWins(document.getInteger("wins")); - - trainer.setLosses(document.getInteger("losses")); - - List pastOpponentsIds = document.getList("pastOpponents", ObjectId.class) - .stream() - .map(ObjectId::toString) - .collect(Collectors.toList()); - trainer.setPastOpponents(pastOpponentsIds); - - Set pokemongList = document - .getList("pokemongs", Document.class) - .stream() - .map(pokemongDoc -> { - TrainerPokemong pokemong = new TrainerPokemong(); - pokemong.setId(((ObjectId) pokemongDoc.get("_id")).toString()); - pokemong.setNickname(pokemongDoc.getString("nickname")); - pokemong.setSpecies(PokemongName.valueOf(pokemongDoc.getString("species"))); - return pokemong; - }) - .collect(Collectors.toSet()); - trainer.setPokemongs(pokemongList); - return trainer; - } } diff --git a/src/main/java/fr/uca/iut/codecs/type/TypeCodecUtil.java b/src/main/java/fr/uca/iut/codecs/type/TypeCodecUtil.java index 89cd1e8..a2d53b6 100644 --- a/src/main/java/fr/uca/iut/codecs/type/TypeCodecUtil.java +++ b/src/main/java/fr/uca/iut/codecs/type/TypeCodecUtil.java @@ -12,15 +12,15 @@ public class TypeCodecUtil { Type type = new Type(); type.setName(TypeName.valueOf(typeDoc.getString("name"))); List weakAgainst = typeDoc.getList("weakAgainst", String.class) - .stream() - .map(TypeName::valueOf) - .collect(Collectors.toList()); + .stream() + .map(TypeName::valueOf) + .collect(Collectors.toList()); type.setWeakAgainst(weakAgainst); List effectiveAgainst = typeDoc.getList("effectiveAgainst", - String.class) - .stream() - .map(TypeName::valueOf) - .collect(Collectors.toList()); + String.class) + .stream() + .map(TypeName::valueOf) + .collect(Collectors.toList()); type.setEffectiveAgainst(effectiveAgainst); return type; } diff --git a/src/main/java/fr/uca/iut/controllers/GenericController.java b/src/main/java/fr/uca/iut/controllers/GenericController.java index ddd16ec..6c11aeb 100644 --- a/src/main/java/fr/uca/iut/controllers/GenericController.java +++ b/src/main/java/fr/uca/iut/controllers/GenericController.java @@ -22,24 +22,23 @@ public abstract class GenericController { T entity = service.getOneById(id); if (entity != null) { return Response.ok(entity) - .build(); - } - else { + .build(); + } else { return Response.status(Response.Status.NOT_FOUND) - .entity("Entity not found for id: " + id) - .build(); + .entity("Entity not found for id: " + id) + .build(); } } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) - .entity("Invalid id format: " + id) - .build(); + .entity(e.getMessage()) + .build(); } } @GET public Response getAll() { return Response.ok(service.getAll()) - .build(); + .build(); } @POST @@ -50,13 +49,13 @@ public abstract class GenericController { T newEntity = service.addOne(entity); return Response.status(Response.Status.CREATED) - .entity(newEntity) - .build(); + .entity(newEntity) + .build(); } catch (NonValidEntityException e) { return Response.status(Response.Status.BAD_REQUEST) - .entity(e.getMessage()) - .build(); + .entity(e.getMessage()) + .build(); } } @@ -70,22 +69,17 @@ public abstract class GenericController { if (updatedEntity != null) { return Response.status(Response.Status.OK) - .entity(updatedEntity) - .build(); - } - else { + .entity(updatedEntity) + .build(); + } else { return Response.status(Response.Status.NOT_FOUND) - .entity("Entity not found for id: " + id) - .build(); + .entity("Entity not found for id: " + id) + .build(); } - } catch (IllegalArgumentException e) { - return Response.status(Response.Status.BAD_REQUEST) - .entity("Invalid id format: " + id) - .build(); - } catch (NonValidEntityException e) { + } catch (IllegalArgumentException | NonValidEntityException e) { return Response.status(Response.Status.BAD_REQUEST) - .entity(e.getMessage()) - .build(); + .entity(e.getMessage()) + .build(); } } @@ -95,12 +89,12 @@ public abstract class GenericController { try { service.deleteOneById(id); return Response.ok() - .build(); + .build(); } catch (IllegalArgumentException e) { return Response.status(Response.Status.BAD_REQUEST) - .entity("Invalid id format: " + id) - .build(); + .entity(e.getMessage()) + .build(); } } } diff --git a/src/main/java/fr/uca/iut/entities/Move.java b/src/main/java/fr/uca/iut/entities/Move.java index 050396c..2aa1242 100644 --- a/src/main/java/fr/uca/iut/entities/Move.java +++ b/src/main/java/fr/uca/iut/entities/Move.java @@ -13,7 +13,8 @@ public class Move extends GenericVersionedEntity { private Integer accuracy; private Type type; - public Move() {} + public Move() { + } public String getName() { return name; diff --git a/src/main/java/fr/uca/iut/entities/Pokemong.java b/src/main/java/fr/uca/iut/entities/Pokemong.java index 2c30cd8..71e0248 100644 --- a/src/main/java/fr/uca/iut/entities/Pokemong.java +++ b/src/main/java/fr/uca/iut/entities/Pokemong.java @@ -31,7 +31,8 @@ public class Pokemong extends GenericVersionedEntity { */ private Set moveSet; - public Pokemong() {} + public Pokemong() { + } @Nullable public String getNickname() { @@ -100,8 +101,7 @@ public class Pokemong extends GenericVersionedEntity { public void updateMove(String id, String name) { for (PokemongMove move : moveSet) { if (move.getId() - .equals(id)) - { + .equals(id)) { move.setName(name); break; } @@ -116,6 +116,10 @@ public class Pokemong extends GenericVersionedEntity { return Collections.unmodifiableList(evoTrack); } + public void setEvoTrack(List evoTrack) { + this.evoTrack = evoTrack; + } + public Integer getEvoStage() { return evoStage; } @@ -123,9 +127,5 @@ public class Pokemong extends GenericVersionedEntity { public void setEvoStage(Integer evoStage) { this.evoStage = evoStage; } - - public void setEvoTrack(List evoTrack) { - this.evoTrack = evoTrack; - } } diff --git a/src/main/java/fr/uca/iut/entities/Trainer.java b/src/main/java/fr/uca/iut/entities/Trainer.java index a8dc22d..a7aae20 100644 --- a/src/main/java/fr/uca/iut/entities/Trainer.java +++ b/src/main/java/fr/uca/iut/entities/Trainer.java @@ -18,7 +18,8 @@ public class Trainer extends GenericVersionedEntity { private List pastOpponents; private Set pokemongs; - public Trainer() {} + public Trainer() { + } public String getName() { return name; @@ -72,7 +73,7 @@ public class Trainer extends GenericVersionedEntity { pokemongs.add(trainerPokemong); } - public void removePokemong(TrainerPokemong trainerPokemong) { - pokemongs.remove(trainerPokemong); + public void removePokemong(String id) { + pokemongs.removeIf(trainerPokemong -> trainerPokemong.getId().equals(id)); } } diff --git a/src/main/java/fr/uca/iut/entities/denormalized/PokemongMove.java b/src/main/java/fr/uca/iut/entities/denormalized/PokemongMove.java index a9da590..6f0b3d8 100644 --- a/src/main/java/fr/uca/iut/entities/denormalized/PokemongMove.java +++ b/src/main/java/fr/uca/iut/entities/denormalized/PokemongMove.java @@ -6,7 +6,8 @@ public class PokemongMove extends GenericEntity { private String name; - public PokemongMove() {} + public PokemongMove() { + } public String getName() { return name; diff --git a/src/main/java/fr/uca/iut/entities/denormalized/TrainerPokemong.java b/src/main/java/fr/uca/iut/entities/denormalized/TrainerPokemong.java index b44bb88..f13a2ee 100644 --- a/src/main/java/fr/uca/iut/entities/denormalized/TrainerPokemong.java +++ b/src/main/java/fr/uca/iut/entities/denormalized/TrainerPokemong.java @@ -10,7 +10,8 @@ public class TrainerPokemong extends GenericEntity { private PokemongName species; - public TrainerPokemong() {} + public TrainerPokemong() { + } @Nullable public String getNickname() { diff --git a/src/main/java/fr/uca/iut/entities/embedded/Type.java b/src/main/java/fr/uca/iut/entities/embedded/Type.java index 452a6bd..38dc8a7 100644 --- a/src/main/java/fr/uca/iut/entities/embedded/Type.java +++ b/src/main/java/fr/uca/iut/entities/embedded/Type.java @@ -11,7 +11,8 @@ public class Type { private List weakAgainst; private List effectiveAgainst; - public Type() {} + public Type() { + } public TypeName getName() { return name; @@ -48,8 +49,8 @@ public class Type { if (o == null || getClass() != o.getClass()) return false; Type type = (Type) o; return Objects.equals(name, type.name) && - Objects.equals(weakAgainst, type.weakAgainst) && - Objects.equals(effectiveAgainst, type.effectiveAgainst); + Objects.equals(weakAgainst, type.weakAgainst) && + Objects.equals(effectiveAgainst, type.effectiveAgainst); } } \ No newline at end of file diff --git a/src/main/java/fr/uca/iut/repositories/GenericRepository.java b/src/main/java/fr/uca/iut/repositories/GenericRepository.java index 649e2a4..71b208a 100644 --- a/src/main/java/fr/uca/iut/repositories/GenericRepository.java +++ b/src/main/java/fr/uca/iut/repositories/GenericRepository.java @@ -2,7 +2,9 @@ package fr.uca.iut.repositories; import com.mongodb.client.MongoClient; import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.ReplaceOneModel; import com.mongodb.client.model.ReplaceOptions; +import com.mongodb.client.model.WriteModel; import com.mongodb.lang.Nullable; import fr.uca.iut.entities.GenericEntity; import org.bson.Document; @@ -27,7 +29,7 @@ public abstract class GenericRepository { @Nullable public T findById(String id) { return getCollection().find(eq("_id", new ObjectId(id))) - .first(); + .first(); } protected abstract MongoCollection getCollection(); @@ -38,7 +40,7 @@ public abstract class GenericRepository { public List listAll() { return getCollection().find() - .into(new ArrayList<>()); + .into(new ArrayList<>()); } public void persistOrUpdate(@NotNull T entity) { @@ -49,6 +51,21 @@ public abstract class GenericRepository { ); } + public void updateAll(@NotNull List entities) { + List> updates = new ArrayList<>(); + for (T entity : entities) { + updates.add( + new ReplaceOneModel<>( + eq("_id", new ObjectId(entity.getId())), + entity, + new ReplaceOptions().upsert(true) + ) + ); + } + + getCollection().bulkWrite(updates); + } + public void delete(@NotNull T entity) { getCollection().deleteOne(eq("_id", new ObjectId(entity.getId()))); } diff --git a/src/main/java/fr/uca/iut/repositories/PokemongRepository.java b/src/main/java/fr/uca/iut/repositories/PokemongRepository.java index 3da2595..6e3c634 100644 --- a/src/main/java/fr/uca/iut/repositories/PokemongRepository.java +++ b/src/main/java/fr/uca/iut/repositories/PokemongRepository.java @@ -33,7 +33,7 @@ public class PokemongRepository extends GenericRepository { public List findByMove(String moveId) { Bson filter = Filters.elemMatch("moveSet", Filters.eq("_id", new ObjectId(moveId))); return getCollection().find(filter) - .into(new ArrayList<>()); + .into(new ArrayList<>()); } @Override diff --git a/src/main/java/fr/uca/iut/services/GenericService.java b/src/main/java/fr/uca/iut/services/GenericService.java index 6f2ab6f..e3bdb41 100644 --- a/src/main/java/fr/uca/iut/services/GenericService.java +++ b/src/main/java/fr/uca/iut/services/GenericService.java @@ -48,11 +48,20 @@ public abstract class GenericService { } /** - * Override me and start with `super.validateOne(entity);` + * Override me */ @Nullable public T updateOne(@NotNull T entity) { validateOne(entity); return entity; } + + public void updateAll(List entities) { + if (!entities.isEmpty()) { + for (T entity : entities) { + validateOne(entity); + } + repository.updateAll(entities); + } + } } diff --git a/src/main/java/fr/uca/iut/services/MoveService.java b/src/main/java/fr/uca/iut/services/MoveService.java index 24f00b4..590de05 100644 --- a/src/main/java/fr/uca/iut/services/MoveService.java +++ b/src/main/java/fr/uca/iut/services/MoveService.java @@ -74,18 +74,20 @@ public class MoveService extends GenericService { @Override public List getAll() { return super.getAll() - .stream() - .map(this::migrateToV2) - .collect(Collectors.toList()); + .stream() + .map(this::migrateToV2) + .collect(Collectors.toList()); } @Override public void deleteOneById(String id) { List pokemongs = pokemongService.findByMove(id); + List pokemongsToUpdate = new ArrayList<>(); for (Pokemong pokemong : pokemongs) { pokemong.removeMove(id); - pokemongService.updateOne(pokemong); + pokemongsToUpdate.add(pokemong); } + pokemongService.updateAll(pokemongsToUpdate); super.deleteOneById(id); } @@ -95,15 +97,9 @@ public class MoveService extends GenericService { super.updateOne(move); Move existingMove = moveRepository.findById(move.getId()); if (existingMove != null) { - if (!existingMove.getName() - .equals(move.getName())) - { + if (!existingMove.getName().equals(move.getName())) { existingMove.setName(move.getName()); - List pokemongs = pokemongService.findByMove(move.getId()); - for (Pokemong pokemong : pokemongs) { - pokemong.updateMove(move.getId(), move.getName()); - pokemongService.updateOne(pokemong); - } + batchUpdatePokemongTrainers(move); } existingMove.setPower(move.getPower()); @@ -115,6 +111,16 @@ public class MoveService extends GenericService { return existingMove; } + private void batchUpdatePokemongTrainers(@NotNull Move move) { + List pokemongs = pokemongService.findByMove(move.getId()); + List pokemongsToUpdate = new ArrayList<>(); + for (Pokemong pokemong : pokemongs) { + pokemong.updateMove(move.getId(), move.getName()); + pokemongsToUpdate.add(pokemong); + } + pokemongService.updateAll(pokemongsToUpdate); + } + /** * We want to migrate the documents incrementally, so we upgrade the * schema version if it is less than the current schema version, diff --git a/src/main/java/fr/uca/iut/services/PokemongService.java b/src/main/java/fr/uca/iut/services/PokemongService.java index a047ef4..a3c0743 100644 --- a/src/main/java/fr/uca/iut/services/PokemongService.java +++ b/src/main/java/fr/uca/iut/services/PokemongService.java @@ -84,17 +84,15 @@ public class PokemongService extends GenericService { Set types = pokemong.getTypes(); if (types == null - || types.size() == 0 - || types.size() > 2) - { + || types.size() == 0 + || types.size() > 2) { errors.add("pokemong types was null or empty or had more than 2 types"); } Set moveSet = pokemong.getMoveSet(); if (moveSet == null) { errors.add("pokemong move set was null"); - } - else { + } else { if (moveSet.size() == 0 || moveSet.size() > 4) { errors.add("pokemong move set was empty or had more than 4 moves"); } @@ -114,8 +112,7 @@ public class PokemongService extends GenericService { } if (pokemong.getSchemaVersion() == null || - !Objects.equals(pokemong.getSchemaVersion(), Pokemong.LATEST_SCHEMA_VERSION)) - { + !Objects.equals(pokemong.getSchemaVersion(), Pokemong.LATEST_SCHEMA_VERSION)) { errors.add( "pokemong schema version was null or not the latest version: " + Pokemong.LATEST_SCHEMA_VERSION); } @@ -131,9 +128,7 @@ public class PokemongService extends GenericService { if (pokemong != null && pokemong.getTrainer() != null) { Trainer trainer = trainerService.getOneById(pokemong.getTrainer()); if (trainer != null) { - trainer.getPokemongs() - .removeIf(trainerPokemong -> trainerPokemong.getId() - .equals(id)); + trainer.removePokemong(id); trainerService.updateOne(trainer); } } @@ -163,32 +158,41 @@ public class PokemongService extends GenericService { pokemongRepository.persistOrUpdate(existingPokemong); if (nicknameChanged || evoStageChanged || evoTrackChanged) { - Trainer trainer = trainerService.getOneById(existingPokemong.getTrainer()); - if (trainer != null) { - TrainerPokemong trainerPokemong = trainer.getPokemongs() - .stream() - .filter(tp -> tp.getId() - .equals(existingPokemong.getId())) - .findFirst() - .orElse(null); - - if (trainerPokemong != null) { - if (nicknameChanged) { - trainerPokemong.setNickname(existingPokemong.getNickname()); - } - - if (evoStageChanged || evoTrackChanged) { - trainerPokemong.setSpecies(existingPokemong.getSpecies()); - } - - trainerService.updateOne(trainer); - } - } + updateTrainerPokemong(existingPokemong, nicknameChanged, evoStageChanged, evoTrackChanged); } } return existingPokemong; } + private void updateTrainerPokemong( + @NotNull Pokemong existingPokemong, + boolean nicknameChanged, + boolean evoStageChanged, + boolean evoTrackChanged + ) { + Trainer trainer = trainerService.getOneById(existingPokemong.getTrainer()); + if (trainer != null) { + TrainerPokemong trainerPokemong = + trainer.getPokemongs() + .stream() + .filter(tp -> tp.getId() + .equals(existingPokemong.getId())) + .findFirst() + .orElse(null); + + if (trainerPokemong != null) { + if (nicknameChanged) { + trainerPokemong.setNickname(existingPokemong.getNickname()); + } + + if (evoStageChanged || evoTrackChanged) { + trainerPokemong.setSpecies(existingPokemong.getSpecies()); + } + trainerService.updateOne(trainer); + } + } + } + public List findByMove(String id) { return pokemongRepository.findByMove(id); } @@ -204,14 +208,15 @@ public class PokemongService extends GenericService { } public void batchUpdatePokemongTrainers(@NotNull Set trainerPokemongs, - @Nullable String trainerId) - { + @Nullable String trainerId) { + List pokemongsToUpdate = new ArrayList<>(); for (TrainerPokemong trainerPokemong : trainerPokemongs) { Pokemong pokemong = getOneById(trainerPokemong.getId()); if (pokemong != null && !Objects.equals(pokemong.getTrainer(), trainerId)) { pokemong.setTrainer(trainerId); - updateOne(pokemong); + pokemongsToUpdate.add(pokemong); } } + updateAll(pokemongsToUpdate); } } diff --git a/src/main/java/fr/uca/iut/services/TrainerService.java b/src/main/java/fr/uca/iut/services/TrainerService.java index 82ccc30..07724ac 100644 --- a/src/main/java/fr/uca/iut/services/TrainerService.java +++ b/src/main/java/fr/uca/iut/services/TrainerService.java @@ -33,7 +33,7 @@ public class TrainerService extends GenericService { public Trainer addOne(@NotNull Trainer trainer) { Trainer persistedTrainer = super.addOne(trainer); - // If this trainer gained pokemongs, that pokemong's ex-trainer if any needs to lose said pokemong + // If this trainer gained pokemongs, that pokemong's ex-trainer if any needs to lose said pokemongs transferNewlyArrivedTrainerPokemongs(new HashSet<>(), persistedTrainer.getPokemongs()); // all owned pokemongs gain this trainer's reference pokemongService.batchUpdatePokemongTrainers(trainer.getPokemongs(), trainer.getId()); @@ -68,8 +68,7 @@ public class TrainerService extends GenericService { if (pastOpponents == null) { errors.add("trainer past opponents collection was null"); - } - else { + } else { for (String trainerId : pastOpponents) { if (StringUtils.isBlankStringOrNull(trainerId)) { errors.add("trainer past opponents collection contained an invalid id: " + trainerId); @@ -81,24 +80,21 @@ public class TrainerService extends GenericService { if (pokemongs == null) { errors.add("trainer pokemongs collection was null or invalid"); - } - else { + } else { for (TrainerPokemong pokemong : pokemongs) { String pokemongId = pokemong.getId(); if (StringUtils.isBlankStringOrNull(pokemongId) || !pokemongService.existsById(pokemongId)) { errors.add("pokemong with id " + pokemongId + " does not exist"); - } - else { + } else { if (!pokemongService.isEvoValid(pokemongId, pokemong.getSpecies())) { errors.add("pokemong with id " + pokemongId + " cannot be a " + - pokemong.getSpecies()); + pokemong.getSpecies()); } Pokemong pokemongBehind = pokemongService.getOneById(pokemongId); if (pokemong.getNickname() != null - && pokemongBehind != null - && !pokemong.getNickname() - .equals(pokemongBehind.getNickname())) - { + && pokemongBehind != null + && !pokemong.getNickname() + .equals(pokemongBehind.getNickname())) { errors.add("pokemong with id " + pokemongId + " already has a nickname"); } } @@ -106,8 +102,7 @@ public class TrainerService extends GenericService { } if (trainer.getSchemaVersion() == null || - !Objects.equals(trainer.getSchemaVersion(), Trainer.LATEST_SCHEMA_VERSION)) - { + !Objects.equals(trainer.getSchemaVersion(), Trainer.LATEST_SCHEMA_VERSION)) { errors.add("trainer schema version was null or not the latest version: " + Trainer.LATEST_SCHEMA_VERSION); } @@ -148,16 +143,16 @@ public class TrainerService extends GenericService { // all old pokemongs who are not there anymore lose their trainer reference pokemongService.batchUpdatePokemongTrainers( oldPokemongs.stream() - .filter(tp -> !newPokemongs.contains(tp)) - .collect(Collectors.toSet()), + .filter(tp -> !newPokemongs.contains(tp)) + .collect(Collectors.toSet()), null); // If this trainer gained a pokemong, that pokemong's ex-trainer if any needs to lose said pokemong transferNewlyArrivedTrainerPokemongs(oldPokemongs, newPokemongs); // all new pokemongs who were not there before gain this trainer's reference pokemongService.batchUpdatePokemongTrainers( newPokemongs.stream() - .filter(tp -> !oldPokemongs.contains(tp)) - .collect(Collectors.toSet()), + .filter(tp -> !oldPokemongs.contains(tp)) + .collect(Collectors.toSet()), existingTrainer.getId()); } return existingTrainer; @@ -166,23 +161,25 @@ public class TrainerService extends GenericService { private void transferNewlyArrivedTrainerPokemongs( @NotNull Set oldPokemongs, @NotNull Set newPokemongs - ) - { - for (TrainerPokemong tp : newPokemongs) { - if (oldPokemongs.isEmpty() || !oldPokemongs.contains(tp)) { - Pokemong pokemong = pokemongService.getOneById(tp.getId()); + ) { + List trainersToUpdate = new ArrayList<>(); + + for (TrainerPokemong newTrainerPokemong : newPokemongs) { + if (oldPokemongs.isEmpty() || !oldPokemongs.contains(newTrainerPokemong)) { + Pokemong pokemong = pokemongService.getOneById(newTrainerPokemong.getId()); if (pokemong != null) { String oldTrainerId = pokemong.getTrainer(); // If the pokemong already had a trainer, remove it from the old trainer's pokemongs list if (oldTrainerId != null) { Trainer oldTrainer = getOneById(oldTrainerId); if (oldTrainer != null) { - oldTrainer.removePokemong(tp); - updateOne(oldTrainer); + oldTrainer.removePokemong(newTrainerPokemong.getId()); + trainersToUpdate.add(oldTrainer); } } } } } + updateAll(trainersToUpdate); } } -- 2.36.3 From 60f4e0085aaa179816fb6a300ca165d32ff7f553 Mon Sep 17 00:00:00 2001 From: Alexis Drai Date: Fri, 2 Jun 2023 11:27:33 +0200 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=93=9D=20Update=20Readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c45f0a6..c5eb299 100644 --- a/README.md +++ b/README.md @@ -131,19 +131,30 @@ You can run the application in dev mode using: ## API testing +### 🧪 Sample dataset + +You can find a sample dataset at `docs/sample-dataset/`. Each JSON file contains a collection. + +To load the `moves` collection into an existing MongoDB cluster, you may use [MongoDB Shell ("mongosh")](https://www.mongodb.com/docs/mongodb-shell/) to run +```shell script +mongoimport --uri=mongodb+srv://:@..mongodb.net/ --collection=moves --file=./docs/sample-dataset/moves.json +``` + ### 🏴‍☠️ SwaggerUI Thanks to this project's OpenAPI specs, you can explore the API in a lot of ways. A popular choice is SwaggerUI -- after you run the app, just go to http://localhost:8080/q/swagger-ui and have fun. ⚠️ Unfortunately, Swagger or Quarkus or SmallRye adds the field `id` to all request examples, but in fact ***you should -not include id** when you POST a new document.* +NOT include id** when you POST or UPDATE a new document.* The app takes care of it for you. Same thing for the field `species` with `Pokemong` documents. ### 🩺 API testing tools You can use an API testing tool such as [Postman](https://www.postman.com/) or [Insomnia](https://insomnia.rest/) to test this app. +If you use Postman, you can even import `docs/postman_collection.json`, designed to work with the `🧪 Sample dataset`. + ### 📱 Front end (later) Moving forward, the front end part of this app -- a different project -- might also come into play for trying out this -- 2.36.3