From 7368e3e3abee88ff202ef036a2c0bff0ee9e5b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Mathieu?= Date: Mon, 23 Dec 2019 11:02:34 +0100 Subject: [PATCH] feat: mongodb-panache-reactive extension fix: rename all this stuff fix: improve test - Use @MongoEntity - Better SSE test feat: optional support chore: move to axle package Merge reactive and blocking API into the same module fix bug #5885 for Axle Remove the deleted reactive extension stage from the CI Documentation guide --- bom/deployment/pom.xml | 5 + docs/src/main/asciidoc/mongodb-panache.adoc | 103 ++ .../PanacheMongoEntityEnhancer.java | 5 - .../deployment/PanacheResourceProcessor.java | 68 ++ .../ReactivePanacheMongoEntityEnhancer.java | 84 ++ ...eactivePanacheMongoRepositoryEnhancer.java | 62 ++ .../panache/mongodb-panache/runtime/pom.xml | 17 +- .../quarkus/mongodb/panache/MongoEntity.java | 0 .../axle/ReactivePanacheMongoEntity.java | 30 + .../axle/ReactivePanacheMongoEntityBase.java | 892 ++++++++++++++++++ .../axle/ReactivePanacheMongoRepository.java | 15 + .../ReactivePanacheMongoRepositoryBase.java | 889 +++++++++++++++++ .../panache/axle/ReactivePanacheQuery.java | 179 ++++ .../axle/runtime/ReactiveMongoOperations.java | 577 +++++++++++ .../runtime/ReactivePanacheQueryImpl.java | 171 ++++ .../CommonQueryBinder.java | 2 +- .../MongoParserVisitor.java | 2 +- .../NativeQueryBinder.java | 4 +- .../PanacheQlQueryBinder.java | 3 +- .../panache/runtime/MongoOperations.java | 2 + .../panache/runtime/MongoPropertyUtil.java | 4 +- integration-tests/mongodb-panache/pom.xml | 4 + .../panache/axle/book/ReactiveBookEntity.java | 81 ++ .../axle/book/ReactiveBookEntityResource.java | 118 +++ .../axle/book/ReactiveBookRepository.java | 10 + .../book/ReactiveBookRepositoryResource.java | 122 +++ .../axle/person/ReactivePersonEntity.java | 12 + .../person/ReactivePersonEntityResource.java | 74 ++ .../axle/person/ReactivePersonRepository.java | 10 + .../ReactivePersonRepositoryResource.java | 81 ++ .../axle/NativeReactiveBookResourceIT.java | 8 + .../axle/ReactiveBookResourceTest.java | 366 +++++++ 32 files changed, 3973 insertions(+), 27 deletions(-) create mode 100644 extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java create mode 100644 extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoRepositoryEnhancer.java mode change 100755 => 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/MongoEntity.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoEntity.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoEntityBase.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoRepository.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoRepositoryBase.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheQuery.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/runtime/ReactiveMongoOperations.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/runtime/ReactivePanacheQueryImpl.java rename extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/{runtime => binder}/CommonQueryBinder.java (97%) rename extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/{runtime => binder}/MongoParserVisitor.java (98%) rename extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/{runtime => binder}/NativeQueryBinder.java (90%) rename extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/{runtime => binder}/PanacheQlQueryBinder.java (95%) create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookEntity.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookEntityResource.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookRepository.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookRepositoryResource.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonEntity.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonEntityResource.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonRepository.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonRepositoryResource.java create mode 100644 integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/axle/NativeReactiveBookResourceIT.java create mode 100644 integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/axle/ReactiveBookResourceTest.java diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index b661ca40d32ae..308011bb46c85 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -321,6 +321,11 @@ quarkus-panache-common-deployment ${project.version} + + io.quarkus + quarkus-mongodb-panache-deployment + ${project.version} + io.quarkus quarkus-mongodb-client-deployment diff --git a/docs/src/main/asciidoc/mongodb-panache.adoc b/docs/src/main/asciidoc/mongodb-panache.adoc index 26853836ae261..d3ce189cfba72 100644 --- a/docs/src/main/asciidoc/mongodb-panache.adoc +++ b/docs/src/main/asciidoc/mongodb-panache.adoc @@ -530,6 +530,109 @@ you need to provide the value by yourself. `ObjectId` can be difficult to use if you want to expose its value in your REST service. So we created JSON-B and Jackson providers to serialize/deserialize them as a `String` which are are automatically registered if your project depends on one of the RESTEasy with JSON-B or RESTEasy with Jackson extensions. +== Reactive Entities and Repositories + +MongoDB with Panache allows using reactive style implementation for both entities and repositories. +For this, you need to use the Reactive variants when defining your entities : `ReactivePanacheMongoEntity` or `ReactivePanacheMongoEntityBase`, +and when defining your repositories: `ReactivePanacheMongoRepository` or `ReactivePanacheMongoRepositoryBase`. + +The reactive variant of the `Person` class will be: + +[source,java] +---- +public class ReactivePerson extends ReactivePanacheMongoEntity { + public String name; + public LocalDate birth; + public Status status; + + // return name as uppercase in the model + public String getName(){ + return name.toUpperCase(); + } + + // store all names in lowercase in the DB + public void setName(String name){ + this.name = name.toLowerCase(); + } +} +---- + +You will have access to the same functionalities of the _imperative_ variant inside the reactive one: bson annotations, custom ID, PanacheQL, ... +But the methods on your entities or repositories will all return reactive types. + +See the equivalent methods from the imperative example with the reactive variant: + +[source,java] +---- +// creating a person +ReactivePerson person = new ReactivePerson(); +person.name = "Loïc"; +person.birth = LocalDate.of(1910, Month.FEBRUARY, 1); +person.status = Status.Alive; + +// persist it +CompletionStage cs1 = person.persist(); + +person.status = Status.Dead; + +// Your must call update() in order to send your entity modifications to MongoDB +CompletionStage cs2 = person.update(); + +// delete it +CompletionStage cs3 = person.delete(); + +// getting a list of all persons +CompletionStage> allPersons = ReactivePerson.listAll(); + +// finding a specific person by ID +CompletionStage personById = ReactivePerson.findById(personId); + +// finding a specific person by ID via an Optional +CompletionStage> optional = ReactivePerson.findByIdOptional(personId); +personById = optional.thenApply(o -> o.orElseThrow(() -> new NotFoundException())); + +// finding all living persons +CompletionStage> livingPersons = ReactivePerson.list("status", Status.Alive); + +// counting all persons +CompletionStage countAll = ReactivePerson.count(); + +// counting all living persons +CompletionStage countAlive = ReactivePerson.count("status", Status.Alive); + +// delete all living persons +CompletionStage deleteCount = ReactivePerson.delete("status", Status.Alive); + +// delete all persons +deleteCount = ReactivePerson.deleteAll(); +---- + +TIP: If you use MongoDB with Panache in conjunction with Resteasy, you can directly return a reactive type inside your JAX-RS resource endpoint as Resteasy will handle it correctly. + +The same query facility exist for the reactive types, but the `stream()` methods acts differently: it returns a reactive stream `Publisher` instead of a `Stream`. + +It allows more advanced reactive use case, for example you can use it to send server-sent events (SSE) via Resteasy: + +[source,java] +---- +import org.jboss.resteasy.annotations.SseElementType; +import org.reactivestreams.Publisher; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +@GET +@Path("/stream") +@Produces(MediaType.SERVER_SENT_EVENTS) +@SseElementType(MediaType.APPLICATION_JSON) +public Publisher streamPersons() { + return ReactivePerson.streamAll(); +} +---- + +TIP: `@SseElementType(MediaType.APPLICATION_JSON)` tells Resteasy to serialize the object in JSON. + + == How and why we simplify MongoDB API When it comes to writing MongoDB entities, there are a number of annoying things that users have grown used to diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java index 27d5b0c216769..0ae97ab3399c2 100644 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java @@ -69,11 +69,6 @@ protected void generateAccessorSetField(MethodVisitor mv, EntityField field) { protected void generateAccessorGetField(MethodVisitor mv, EntityField field) { mv.visitFieldInsn(Opcodes.GETFIELD, thisClass.getInternalName(), field.name, field.descriptor); } - - @Override - public void visitEnd() { - super.visitEnd(); - } } public void collectFields(ClassInfo classInfo) { diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java index 7d67019bc6305..24e94b4ccb6c6 100644 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java @@ -36,10 +36,15 @@ import io.quarkus.mongodb.panache.PanacheMongoRepository; import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase; import io.quarkus.mongodb.panache.ProjectionFor; +import io.quarkus.mongodb.panache.axle.ReactivePanacheMongoEntity; +import io.quarkus.mongodb.panache.axle.ReactivePanacheMongoEntityBase; +import io.quarkus.mongodb.panache.axle.ReactivePanacheMongoRepository; +import io.quarkus.mongodb.panache.axle.ReactivePanacheMongoRepositoryBase; import io.quarkus.panache.common.deployment.PanacheFieldAccessEnhancer; import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; public class PanacheResourceProcessor { + // blocking types static final DotName DOTNAME_PANACHE_REPOSITORY_BASE = DotName.createSimple(PanacheMongoRepositoryBase.class.getName()); private static final DotName DOTNAME_PANACHE_REPOSITORY = DotName.createSimple(PanacheMongoRepository.class.getName()); static final DotName DOTNAME_PANACHE_ENTITY_BASE = DotName.createSimple(PanacheMongoEntityBase.class.getName()); @@ -48,6 +53,15 @@ public class PanacheResourceProcessor { private static final DotName DOTNAME_PROJECTION_FOR = DotName.createSimple(ProjectionFor.class.getName()); private static final DotName DOTNAME_BSON_PROPERTY = DotName.createSimple(BsonProperty.class.getName()); + // reactive types: Axle + static final DotName DOTNAME_AXLE_PANACHE_REPOSITORY_BASE = DotName + .createSimple(ReactivePanacheMongoRepositoryBase.class.getName()); + private static final DotName DOTNAME_AXLE_PANACHE_REPOSITORY = DotName + .createSimple(ReactivePanacheMongoRepository.class.getName()); + static final DotName DOTNAME_AXLE_PANACHE_ENTITY_BASE = DotName + .createSimple(ReactivePanacheMongoEntityBase.class.getName()); + private static final DotName DOTNAME_AXLE_PANACHE_ENTITY = DotName.createSimple(ReactivePanacheMongoEntity.class.getName()); + private static final DotName DOTNAME_OBJECT_ID = DotName.createSimple(ObjectId.class.getName()); private static final DotName DOTNAME_OBJECT = DotName.createSimple(Object.class.getName()); @@ -198,4 +212,58 @@ private void extractMappings(Map classPropertyMapping, ClassInfo extractMappings(classPropertyMapping, superClass, index); } } + + @BuildStep + void buildAxle(CombinedIndexBuildItem index, + ApplicationIndexBuildItem applicationIndex, + BuildProducer transformers) throws Exception { + + ReactivePanacheMongoRepositoryEnhancer daoEnhancer = new ReactivePanacheMongoRepositoryEnhancer(index.getIndex()); + Set daoClasses = new HashSet<>(); + for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_AXLE_PANACHE_REPOSITORY_BASE)) { + // Skip PanacheRepository + if (classInfo.name().equals(DOTNAME_AXLE_PANACHE_REPOSITORY)) + continue; + if (PanacheRepositoryEnhancer.skipRepository(classInfo)) + continue; + daoClasses.add(classInfo.name().toString()); + } + for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_AXLE_PANACHE_REPOSITORY)) { + if (PanacheRepositoryEnhancer.skipRepository(classInfo)) + continue; + daoClasses.add(classInfo.name().toString()); + } + for (String daoClass : daoClasses) { + transformers.produce(new BytecodeTransformerBuildItem(daoClass, daoEnhancer)); + } + + ReactivePanacheMongoEntityEnhancer modelEnhancer = new ReactivePanacheMongoEntityEnhancer(index.getIndex()); + Set modelClasses = new HashSet<>(); + // Note that we do this in two passes because for some reason Jandex does not give us subtypes + // of PanacheMongoEntity if we ask for subtypes of PanacheMongoEntityBase + for (ClassInfo classInfo : index.getIndex().getAllKnownSubclasses(DOTNAME_AXLE_PANACHE_ENTITY_BASE)) { + if (classInfo.name().equals(DOTNAME_AXLE_PANACHE_ENTITY)) + continue; + if (modelClasses.add(classInfo.name().toString())) + modelEnhancer.collectFields(classInfo); + } + for (ClassInfo classInfo : index.getIndex().getAllKnownSubclasses(DOTNAME_AXLE_PANACHE_ENTITY)) { + if (modelClasses.add(classInfo.name().toString())) + modelEnhancer.collectFields(classInfo); + } + for (String modelClass : modelClasses) { + transformers.produce(new BytecodeTransformerBuildItem(modelClass, modelEnhancer)); + } + + if (!modelEnhancer.entities.isEmpty()) { + PanacheFieldAccessEnhancer panacheFieldAccessEnhancer = new PanacheFieldAccessEnhancer( + modelEnhancer.getModelInfo()); + for (ClassInfo classInfo : applicationIndex.getIndex().getKnownClasses()) { + String className = classInfo.name().toString(); + if (!modelClasses.contains(className)) { + transformers.produce(new BytecodeTransformerBuildItem(className, panacheFieldAccessEnhancer)); + } + } + } + } } diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java new file mode 100644 index 0000000000000..b425ded06c775 --- /dev/null +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java @@ -0,0 +1,84 @@ +package io.quarkus.mongodb.panache.deployment; + +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; + +import org.bson.codecs.pojo.annotations.BsonIgnore; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import io.quarkus.gizmo.DescriptorUtils; +import io.quarkus.mongodb.panache.axle.runtime.ReactiveMongoOperations; +import io.quarkus.panache.common.deployment.EntityField; +import io.quarkus.panache.common.deployment.EntityModel; +import io.quarkus.panache.common.deployment.MetamodelInfo; +import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; + +public class ReactivePanacheMongoEntityEnhancer extends PanacheEntityEnhancer>> { + public final static String MONGO_OPERATIONS_NAME = ReactiveMongoOperations.class.getName(); + public final static String MONGO_OPERATIONS_BINARY_NAME = MONGO_OPERATIONS_NAME.replace('.', '/'); + + private static final DotName DOTNAME_BSON_IGNORE = DotName.createSimple(BsonIgnore.class.getName()); + + final Map entities = new HashMap<>(); + + public ReactivePanacheMongoEntityEnhancer(IndexView index) { + super(index, PanacheResourceProcessor.DOTNAME_AXLE_PANACHE_ENTITY_BASE); + modelInfo = new MetamodelInfo<>(); + } + + @Override + public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { + return new PanacheMongoEntityClassVisitor(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo); + } + + static class PanacheMongoEntityClassVisitor extends PanacheEntityClassVisitor { + + public PanacheMongoEntityClassVisitor(String className, ClassVisitor outputClassVisitor, + MetamodelInfo> modelInfo, ClassInfo panacheEntityBaseClassInfo) { + super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo); + } + + @Override + protected void injectModel(MethodVisitor mv) { + mv.visitLdcInsn(thisClass); + } + + @Override + protected String getModelDescriptor() { + return "Ljava/lang/Class;"; + } + + @Override + protected String getPanacheOperationsBinaryName() { + return MONGO_OPERATIONS_BINARY_NAME; + } + + @Override + protected void generateAccessorSetField(MethodVisitor mv, EntityField field) { + mv.visitFieldInsn(Opcodes.PUTFIELD, thisClass.getInternalName(), field.name, field.descriptor); + } + + @Override + protected void generateAccessorGetField(MethodVisitor mv, EntityField field) { + mv.visitFieldInsn(Opcodes.GETFIELD, thisClass.getInternalName(), field.name, field.descriptor); + } + } + + public void collectFields(ClassInfo classInfo) { + EntityModel entityModel = new EntityModel<>(classInfo); + for (FieldInfo fieldInfo : classInfo.fields()) { + String name = fieldInfo.name(); + if (Modifier.isPublic(fieldInfo.flags()) && !fieldInfo.hasAnnotation(DOTNAME_BSON_IGNORE)) { + entityModel.addField(new EntityField(name, DescriptorUtils.typeToString(fieldInfo.type()))); + } + } + modelInfo.addEntityModel(entityModel); + } +} diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoRepositoryEnhancer.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoRepositoryEnhancer.java new file mode 100644 index 0000000000000..b26938f15d55c --- /dev/null +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoRepositoryEnhancer.java @@ -0,0 +1,62 @@ +package io.quarkus.mongodb.panache.deployment; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +import io.quarkus.mongodb.panache.axle.ReactivePanacheMongoRepository; +import io.quarkus.mongodb.panache.axle.ReactivePanacheMongoRepositoryBase; +import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; + +public class ReactivePanacheMongoRepositoryEnhancer extends PanacheRepositoryEnhancer { + public final static DotName PANACHE_REPOSITORY_BASE_NAME = DotName + .createSimple(ReactivePanacheMongoRepositoryBase.class.getName()); + + public final static DotName PANACHE_REPOSITORY_NAME = DotName.createSimple(ReactivePanacheMongoRepository.class.getName()); + + public ReactivePanacheMongoRepositoryEnhancer(IndexView index) { + super(index, PanacheResourceProcessor.DOTNAME_AXLE_PANACHE_REPOSITORY_BASE); + } + + @Override + public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { + return new PanacheMongoRepositoryClassVisitor(className, outputClassVisitor, panacheRepositoryBaseClassInfo, + this.indexView); + } + + static class PanacheMongoRepositoryClassVisitor extends PanacheRepositoryClassVisitor { + + public PanacheMongoRepositoryClassVisitor(String className, ClassVisitor outputClassVisitor, + ClassInfo panacheRepositoryBaseClassInfo, IndexView indexView) { + super(className, outputClassVisitor, panacheRepositoryBaseClassInfo, indexView); + } + + @Override + protected DotName getPanacheRepositoryDotName() { + return PANACHE_REPOSITORY_NAME; + } + + @Override + protected DotName getPanacheRepositoryBaseDotName() { + return PANACHE_REPOSITORY_BASE_NAME; + } + + @Override + protected String getPanacheOperationsBinaryName() { + return ReactivePanacheMongoEntityEnhancer.MONGO_OPERATIONS_BINARY_NAME; + } + + @Override + protected void injectModel(MethodVisitor mv) { + // inject Class + mv.visitLdcInsn(entityType); + } + + @Override + protected String getModelDescriptor() { + return "Ljava/lang/Class;"; + } + } +} diff --git a/extensions/panache/mongodb-panache/runtime/pom.xml b/extensions/panache/mongodb-panache/runtime/pom.xml index 1efc566d6e9e8..1a6f1d7b7451f 100644 --- a/extensions/panache/mongodb-panache/runtime/pom.xml +++ b/extensions/panache/mongodb-panache/runtime/pom.xml @@ -24,11 +24,11 @@ io.quarkus - quarkus-panacheql + quarkus-mongodb-client io.quarkus - quarkus-mongodb-client + quarkus-panacheql @@ -74,19 +74,6 @@ - - - - ${project.build.directory}/classes - - - **/PanacheMongoJsonbContextResolver.class - - **/ObjectMapperProducer.class - - - - diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/MongoEntity.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/MongoEntity.java old mode 100755 new mode 100644 diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoEntity.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoEntity.java new file mode 100644 index 0000000000000..6218b3ba67b7a --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoEntity.java @@ -0,0 +1,30 @@ +package io.quarkus.mongodb.panache.axle; + +import org.bson.types.ObjectId; + +/** + * Represents an entity with a generated ID field {@link #id} of type {@link ObjectId}. If your + * Mongo entities extend this class they gain the ID field and auto-generated accessors + * to all their public fields, as well as all the useful methods from {@link ReactivePanacheMongoEntityBase}. + * + * If you want a custom ID type or strategy, you can directly extend {@link ReactivePanacheMongoEntityBase} + * instead, and write your own ID field. You will still get auto-generated accessors and + * all the useful methods. + * + * @see ReactivePanacheMongoEntityBase + */ +public abstract class ReactivePanacheMongoEntity extends ReactivePanacheMongoEntityBase { + + /** + * The auto-generated ID field. + * This field is set by Mongo when this entity is persisted. + * + * @see #persist() + */ + public ObjectId id; + + @Override + public String toString() { + return this.getClass().getSimpleName() + "<" + id + ">"; + } +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoEntityBase.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoEntityBase.java new file mode 100644 index 0000000000000..59367d60f8287 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoEntityBase.java @@ -0,0 +1,892 @@ +package io.quarkus.mongodb.panache.axle; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; + +import org.bson.Document; +import org.reactivestreams.Publisher; + +import io.quarkus.mongodb.ReactiveMongoCollection; +import io.quarkus.mongodb.ReactiveMongoDatabase; +import io.quarkus.mongodb.panache.axle.runtime.ReactiveMongoOperations; +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; +import io.quarkus.panache.common.impl.GenerateBridge; + +/** + * Represents an entity. If your Mongo entities extend this class they gain auto-generated accessors + * to all their public fields, as well as a lot of useful + * methods. Unless you have a custom ID strategy, you should not extend this class directly but extend + * {@link ReactivePanacheMongoEntity} instead. + * + * @see ReactivePanacheMongoEntity + */ +public abstract class ReactivePanacheMongoEntityBase { + + // Operations + + /** + * Persist this entity in the database. + * This will set it's ID field if not already set. + * + * @see #persist(Iterable) + * @see #persist(Stream) + * @see #persist(Object, Object...) + */ + public CompletionStage persist() { + return ReactiveMongoOperations.persist(this); + } + + /** + * Update this entity in the database. + * + * @see #update(Iterable) + * @see #update(Stream) + * @see #update(Object, Object...) + */ + public CompletionStage update() { + return ReactiveMongoOperations.update(this); + } + + /** + * Persist this entity in the database or update it if it already exist. + * + * @see #persistOrUpdate(Iterable) + * @see #persistOrUpdate(Stream) + * @see #persistOrUpdate(Object, Object...) + */ + public CompletionStage persistOrUpdate() { + return ReactiveMongoOperations.persistOrUpdate(this); + } + + /** + * Delete this entity from the database, if it is already persisted. + * + * @see #delete(String, Object...) + * @see #delete(String, Map) + * @see #delete(String, Parameters) + * @see #deleteAll() + */ + public CompletionStage delete() { + return ReactiveMongoOperations.delete(this); + } + + // Queries + + /** + * Find an entity of this type by ID. + * + * @param id the ID of the entity to find. + * @return the entity found, or null if not found. + */ + @GenerateBridge + public static CompletionStage findById(Object id) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find an entity of this type by ID. + * + * @param id the ID of the entity to find. + * @return if found, an optional containing the entity, else Optional.empty(). + */ + @GenerateBridge + public static CompletionStage> findByIdOptional(Object id) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query, with optional indexed parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params optional sequence of indexed parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Sort, Object...) + * @see #find(String, Map) + * @see #find(String, Parameters) + * @see #list(String, Object...) + * @see #stream(String, Object...) + */ + @GenerateBridge + public static ReactivePanacheQuery find(String query, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query and the given sort options, with optional indexed parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Object...) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Object...) + * @see #stream(String, Sort, Object...) + */ + @GenerateBridge + public static ReactivePanacheQuery find(String query, Sort sort, + Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Map} of named parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Sort, Map) + * @see #find(String, Object...) + * @see #find(String, Parameters) + * @see #list(String, Map) + * @see #stream(String, Map) + */ + @GenerateBridge + public static ReactivePanacheQuery find(String query, + Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query and the given sort options, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Map} of indexed parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Map) + * @see #find(String, Sort, Object...) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Map) + * @see #stream(String, Sort, Map) + */ + @GenerateBridge + public static ReactivePanacheQuery find(String query, Sort sort, + Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Parameters} of named parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Sort, Parameters) + * @see #find(String, Map) + * @see #find(String, Parameters) + * @see #list(String, Parameters) + * @see #stream(String, Parameters) + */ + @GenerateBridge + public static ReactivePanacheQuery find(String query, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query and the given sort options, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Parameters} of indexed parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public static ReactivePanacheQuery find(String query, Sort sort, + Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a BSON query. + * + * @param query a {@link Document} query + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public static ReactivePanacheQuery find(Document query) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a a BSON query and a BSON sort. + * + * @param query a {@link Document} query + * @param sort the {@link Document} sort + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public static ReactivePanacheQuery find(Document query, Document sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type. + * + * @return a new {@link ReactivePanacheQuery} instance to find all entities of this type. + * @see #findAll(Sort) + * @see #listAll() + * @see #streamAll() + */ + @GenerateBridge + public static ReactivePanacheQuery findAll() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type, in the given order. + * + * @param sort the sort order to use + * @return a new {@link ReactivePanacheQuery} instance to find all entities of this type. + * @see #findAll() + * @see #listAll(Sort) + * @see #streamAll(Sort) + */ + @GenerateBridge + public static ReactivePanacheQuery findAll(Sort sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for find(query, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params optional sequence of indexed parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Sort, Object...) + * @see #list(String, Map) + * @see #list(String, Parameters) + * @see #find(String, Object...) + * @see #stream(String, Object...) + */ + @GenerateBridge + public static CompletionStage> list(String query, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with optional indexed parameters. + * This method is a shortcut for find(query, sort, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Object...) + * @see #list(String, Sort, Map) + * @see #list(String, Sort, Parameters) + * @see #find(String, Sort, Object...) + * @see #stream(String, Sort, Object...) + */ + @GenerateBridge + public static CompletionStage> list(String query, Sort sort, + Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for find(query, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Map} of named parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Sort, Map) + * @see #list(String, Object...) + * @see #list(String, Parameters) + * @see #find(String, Map) + * @see #stream(String, Map) + */ + @GenerateBridge + public static CompletionStage> list(String query, + Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with named parameters. + * This method is a shortcut for find(query, sort, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Map} of indexed parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Map) + * @see #list(String, Sort, Object...) + * @see #list(String, Sort, Parameters) + * @see #find(String, Sort, Map) + * @see #stream(String, Sort, Map) + */ + @GenerateBridge + public static CompletionStage> list(String query, Sort sort, + Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for find(query, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Parameters} of named parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Sort, Parameters) + * @see #list(String, Object...) + * @see #list(String, Map) + * @see #find(String, Parameters) + * @see #stream(String, Parameters) + */ + @GenerateBridge + public static CompletionStage> list(String query, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with named parameters. + * This method is a shortcut for find(query, sort, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Parameters} of indexed parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Parameters) + * @see #list(String, Sort, Object...) + * @see #list(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public static CompletionStage> list(String query, Sort sort, + Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a BSON query. + * This method is a shortcut for find(query).list(). + * + * @param query a {@link Document} query + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public static CompletionStage> list(Document query) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a a BSON query and a BSON sort. + * This method is a shortcut for find(query, sort).list(). + * + * @param query a {@link Document} query + * @param sort the {@link Document} sort + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public static CompletionStage> list(Document query, Document sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type. + * This method is a shortcut for findAll().list(). + * + * @return a {@link List} containing all results, without paging + * @see #listAll(Sort) + * @see #findAll() + * @see #streamAll() + */ + @GenerateBridge + public static CompletionStage> listAll() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type, in the given order. + * This method is a shortcut for findAll(sort).list(). + * + * @param sort the sort order to use + * @return a {@link List} containing all results, without paging + * @see #listAll() + * @see #findAll(Sort) + * @see #streamAll(Sort) + */ + @GenerateBridge + public static CompletionStage> listAll(Sort sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for find(query, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params optional sequence of indexed parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Sort, Object...) + * @see #stream(String, Map) + * @see #stream(String, Parameters) + * @see #find(String, Object...) + * @see #list(String, Object...) + */ + @GenerateBridge + public static Publisher stream(String query, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with optional indexed parameters. + * This method is a shortcut for find(query, sort, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Object...) + * @see #stream(String, Sort, Map) + * @see #stream(String, Sort, Parameters) + * @see #find(String, Sort, Object...) + * @see #list(String, Sort, Object...) + */ + @GenerateBridge + public static Publisher stream(String query, Sort sort, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for find(query, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Map} of named parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Sort, Map) + * @see #stream(String, Object...) + * @see #stream(String, Parameters) + * @see #find(String, Map) + * @see #list(String, Map) + */ + @GenerateBridge + public static Publisher stream(String query, Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with named parameters. + * This method is a shortcut for find(query, sort, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Map} of indexed parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Map) + * @see #stream(String, Sort, Object...) + * @see #stream(String, Sort, Parameters) + * @see #find(String, Sort, Map) + * @see #list(String, Sort, Map) + */ + @GenerateBridge + public static Publisher stream(String query, Sort sort, + Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for find(query, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Parameters} of named parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Sort, Parameters) + * @see #stream(String, Object...) + * @see #stream(String, Map) + * @see #find(String, Parameters) + * @see #list(String, Parameters) + */ + @GenerateBridge + public static Publisher stream(String query, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with named parameters. + * This method is a shortcut for find(query, sort, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Parameters} of indexed parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Parameters) + * @see #stream(String, Sort, Object...) + * @see #stream(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + */ + @GenerateBridge + public static Publisher stream(String query, Sort sort, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a BSON query. + * This method is a shortcut for find(query).stream(). + * + * @param query a {@link Document} query + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public static Publisher stream(Document query) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a a BSON query and a BSON sort. + * This method is a shortcut for find(query, sort).stream(). + * + * @param query a {@link Document} query + * @param sort the {@link Document} sort + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public static Publisher stream(Document query, Document sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type. + * This method is a shortcut for findAll().stream(). + * + * @return a {@link Stream} containing all results, without paging + * @see #streamAll(Sort) + * @see #findAll() + * @see #listAll() + */ + @GenerateBridge + public static Publisher streamAll() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type, in the given order. + * This method is a shortcut for findAll(sort).stream(). + * + * @param sort the sort order to use + * @return a {@link Stream} containing all results, without paging + * @see #streamAll() + * @see #findAll(Sort) + * @see #listAll(Sort) + */ + @GenerateBridge + public static Publisher streamAll(Sort sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Counts the number of this type of entity in the database. + * + * @return the number of this type of entity in the database. + * @see #count(String, Object...) + * @see #count(String, Map) + * @see #count(String, Parameters) + */ + @GenerateBridge + public static CompletionStage count() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Counts the number of this type of entity matching the given query, with optional indexed parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params optional sequence of indexed parameters + * @return the number of entities counted. + * @see #count() + * @see #count(String, Map) + * @see #count(String, Parameters) + */ + @GenerateBridge + public static CompletionStage count(String query, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Counts the number of this type of entity matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Map} of named parameters + * @return the number of entities counted. + * @see #count() + * @see #count(String, Object...) + * @see #count(String, Parameters) + */ + @GenerateBridge + public static CompletionStage count(String query, Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Counts the number of this type of entity matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Parameters} of named parameters + * @return the number of entities counted. + * @see #count() + * @see #count(String, Object...) + * @see #count(String, Map) + */ + @GenerateBridge + public static CompletionStage count(String query, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Counts the number of this type of entity matching the given query + * + * @param query a {@link Document} query + * @return he number of entities counted. + * @see #count() + * @see #count(String, Object...) + * @see #count(String, Map) + */ + @GenerateBridge + public static CompletionStage count(Document query) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Delete all entities of this type from the database. + * + * @return the number of entities deleted. + * @see #delete(String, Object...) + * @see #delete(String, Map) + * @see #delete(String, Parameters) + */ + @GenerateBridge + public static CompletionStage deleteAll() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Delete all entities of this type matching the given query, with optional indexed parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params optional sequence of indexed parameters + * @return the number of entities deleted. + * @see #deleteAll() + * @see #delete(String, Map) + * @see #delete(String, Parameters) + */ + @GenerateBridge + public static CompletionStage delete(String query, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Map} of named parameters + * @return the number of entities deleted. + * @see #deleteAll() + * @see #delete(String, Object...) + * @see #delete(String, Parameters) + */ + @GenerateBridge + public static CompletionStage delete(String query, Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Parameters} of named parameters + * @return the number of entities deleted. + * @see #deleteAll() + * @see #delete(String, Object...) + * @see #delete(String, Map) + */ + @GenerateBridge + public static CompletionStage delete(String query, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Delete all entities of this type matching the given query + * + * @param query a {@link Document} query + * @return he number of entities counted. + * @see #count() + * @see #count(String, Object...) + * @see #count(String, Map) + */ + @GenerateBridge + public static CompletionStage delete(Document query) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Insert all given entities. + * + * @param entities the entities to insert + * @see #persist() + * @see #persist(Stream) + * @see #persist(Object,Object...) + */ + public static CompletionStage persist(Iterable entities) { + return ReactiveMongoOperations.persist(entities); + } + + /** + * Insert all given entities. + * + * @param entities the entities to insert + * @see #persist() + * @see #persist(Iterable) + * @see #persist(Object,Object...) + */ + public static CompletionStage persist(Stream entities) { + return ReactiveMongoOperations.persist(entities); + } + + /** + * Insert all given entities. + * + * @param entities the entities to update + * @see #persist() + * @see #persist(Stream) + * @see #persist(Iterable) + */ + public static CompletionStage persist(Object firstEntity, Object... entities) { + return ReactiveMongoOperations.persist(firstEntity, entities); + } + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see #update() + * @see #update(Stream) + * @see #update(Object,Object...) + */ + public static CompletionStage update(Iterable entities) { + return ReactiveMongoOperations.update(entities); + } + + /** + * Update all given entities. + * + * @param entities the entities to insert + * @see #update() + * @see #update(Iterable) + * @see #update(Object,Object...) + */ + public static CompletionStage update(Stream entities) { + return ReactiveMongoOperations.update(entities); + } + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see #update() + * @see #update(Stream) + * @see #update(Iterable) + */ + public static CompletionStage update(Object firstEntity, Object... entities) { + return ReactiveMongoOperations.update(firstEntity, entities); + } + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see #persistOrUpdate() + * @see #persistOrUpdate(Stream) + * @see #persistOrUpdate(Object,Object...) + */ + public static CompletionStage persistOrUpdate(Iterable entities) { + return ReactiveMongoOperations.persistOrUpdate(entities); + } + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see #persistOrUpdate() + * @see #persistOrUpdate(Iterable) + * @see #persistOrUpdate(Object,Object...) + */ + public static CompletionStage persistOrUpdate(Stream entities) { + return ReactiveMongoOperations.persistOrUpdate(entities); + } + + /** + * Persist all given entities. + * + * @param entities the entities to update + * @see #persistOrUpdate() + * @see #persistOrUpdate(Stream) + * @see #persistOrUpdate(Iterable) + */ + public static CompletionStage persistOrUpdate(Object firstEntity, Object... entities) { + return ReactiveMongoOperations.persistOrUpdate(firstEntity, entities); + } + + /** + * Allow to access the underlying Mongo Collection. + */ + @GenerateBridge + public static ReactiveMongoCollection mongoCollection() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Allow to access the underlying Mongo Database. + */ + @GenerateBridge + public static ReactiveMongoDatabase mongoDatabase() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoRepository.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoRepository.java new file mode 100644 index 0000000000000..e64de1e7a0554 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoRepository.java @@ -0,0 +1,15 @@ +package io.quarkus.mongodb.panache.axle; + +import org.bson.types.ObjectId; + +/** + * Represents a Repository for a specific type of entity {@code Entity}, with an ID type + * of {@code ObjectId}. Implementing this repository will gain you the exact same useful methods + * that are on {@link ReactivePanacheMongoEntityBase}. If you have a custom ID strategy, you should + * implement {@link ReactivePanacheMongoRepositoryBase} instead. + * + * @param The type of entity to operate on + */ +public interface ReactivePanacheMongoRepository extends ReactivePanacheMongoRepositoryBase { + +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoRepositoryBase.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoRepositoryBase.java new file mode 100644 index 0000000000000..61db1b7959b43 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheMongoRepositoryBase.java @@ -0,0 +1,889 @@ +package io.quarkus.mongodb.panache.axle; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; + +import org.bson.Document; +import org.reactivestreams.Publisher; + +import io.quarkus.mongodb.ReactiveMongoCollection; +import io.quarkus.mongodb.ReactiveMongoDatabase; +import io.quarkus.mongodb.panache.axle.runtime.ReactiveMongoOperations; +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; +import io.quarkus.panache.common.impl.GenerateBridge; + +/** + * Represents a Repository for a specific type of entity {@code Entity}, with an ID type + * of {@code Id}. Implementing this repository will gain you the exact same useful methods + * that are on {@link ReactivePanacheMongoEntityBase}. Unless you have a custom ID strategy, you should not + * implement this interface directly but implement {@link ReactivePanacheMongoRepository} instead. + * + * @param The type of entity to operate on + * @param The ID type of the entity + * @see ReactivePanacheMongoRepository + */ +public interface ReactivePanacheMongoRepositoryBase { + + // Operations + + /** + * Persist the given entity in the database. + * This will set it's ID field if not already set. + * + * @param entity the entity to insert. + * @see #persist(Iterable) + * @see #persist(Stream) + * @see #persist(Object, Object...) + */ + public default CompletionStage persist(Entity entity) { + return ReactiveMongoOperations.persist(entity); + } + + /** + * Update the given entity in the database. + * + * @param entity the entity to update. + * @see #update(Iterable) + * @see #update(Stream) + * @see #update(Object, Object...) + */ + public default CompletionStage update(Entity entity) { + return ReactiveMongoOperations.update(entity); + } + + /** + * Persist the given entity in the database or update it if it already exist. + * + * @param entity the entity to update. + * @see #persistOrUpdate(Iterable) + * @see #persistOrUpdate(Stream) + * @see #persistOrUpdate(Object, Object...) + */ + public default CompletionStage persistOrUpdate(Entity entity) { + return ReactiveMongoOperations.persistOrUpdate(entity); + } + + /** + * Delete the given entity from the database, if it is already persisted. + * + * @param entity the entity to delete. + * @see #delete(String, Object...) + * @see #delete(String, Map) + * @see #delete(String, Parameters) + * @see #deleteAll() + */ + public default CompletionStage delete(Entity entity) { + return ReactiveMongoOperations.delete(entity); + } + + // Queries + + /** + * Find an entity of this type by ID. + * + * @param id the ID of the entity to find. + * @return the entity found, or null if not found. + */ + @GenerateBridge + public default CompletionStage findById(Id id) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find an entity of this type by ID. + * + * @param id the ID of the entity to find. + * @return if found, an optional containing the entity, else Optional.empty(). + */ + @GenerateBridge + public default CompletionStage> findByIdOptional(Object id) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query, with optional indexed parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params optional sequence of indexed parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Sort, Object...) + * @see #find(String, Map) + * @see #find(String, Parameters) + * @see #list(String, Object...) + * @see #stream(String, Object...) + */ + @GenerateBridge + public default ReactivePanacheQuery find(String query, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query and the given sort options, with optional indexed parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Object...) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Object...) + * @see #stream(String, Sort, Object...) + */ + @GenerateBridge + public default ReactivePanacheQuery find(String query, Sort sort, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Map} of named parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Sort, Map) + * @see #find(String, Object...) + * @see #find(String, Parameters) + * @see #list(String, Map) + * @see #stream(String, Map) + */ + @GenerateBridge + public default ReactivePanacheQuery find(String query, Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query and the given sort options, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Map} of indexed parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Map) + * @see #find(String, Sort, Object...) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Map) + * @see #stream(String, Sort, Map) + */ + @GenerateBridge + public default ReactivePanacheQuery find(String query, Sort sort, Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Parameters} of named parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Sort, Parameters) + * @see #find(String, Map) + * @see #find(String, Parameters) + * @see #list(String, Parameters) + * @see #stream(String, Parameters) + */ + @GenerateBridge + public default ReactivePanacheQuery find(String query, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a query and the given sort options, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Parameters} of indexed parameters + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public default ReactivePanacheQuery find(String query, Sort sort, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a BSON query. + * + * @param query a {@link Document} query + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public default ReactivePanacheQuery find(Document query) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a a BSON query and a BSON sort. + * + * @param query a {@link Document} query + * @param sort the {@link Document} sort + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public default ReactivePanacheQuery find(Document query, Document sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type. + * + * @return a new {@link ReactivePanacheQuery} instance to find all entities of this type. + * @see #findAll(Sort) + * @see #listAll() + * @see #streamAll() + */ + @GenerateBridge + public default ReactivePanacheQuery findAll() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type, in the given order. + * + * @param sort the sort order to use + * @return a new {@link ReactivePanacheQuery} instance to find all entities of this type. + * @see #findAll() + * @see #listAll(Sort) + * @see #streamAll(Sort) + */ + @GenerateBridge + public default ReactivePanacheQuery findAll(Sort sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for find(query, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params optional sequence of indexed parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Sort, Object...) + * @see #list(String, Map) + * @see #list(String, Parameters) + * @see #find(String, Object...) + * @see #stream(String, Object...) + */ + @GenerateBridge + public default CompletionStage> list(String query, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with optional indexed parameters. + * This method is a shortcut for find(query, sort, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Object...) + * @see #list(String, Sort, Map) + * @see #list(String, Sort, Parameters) + * @see #find(String, Sort, Object...) + * @see #stream(String, Sort, Object...) + */ + @GenerateBridge + public default CompletionStage> list(String query, Sort sort, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for find(query, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Map} of named parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Sort, Map) + * @see #list(String, Object...) + * @see #list(String, Parameters) + * @see #find(String, Map) + * @see #stream(String, Map) + */ + @GenerateBridge + public default CompletionStage> list(String query, Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with named parameters. + * This method is a shortcut for find(query, sort, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Map} of indexed parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Map) + * @see #list(String, Sort, Object...) + * @see #list(String, Sort, Parameters) + * @see #find(String, Sort, Map) + * @see #stream(String, Sort, Map) + */ + @GenerateBridge + public default CompletionStage> list(String query, Sort sort, Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for find(query, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Parameters} of named parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Sort, Parameters) + * @see #list(String, Object...) + * @see #list(String, Map) + * @see #find(String, Parameters) + * @see #stream(String, Parameters) + */ + @GenerateBridge + public default CompletionStage> list(String query, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with named parameters. + * This method is a shortcut for find(query, sort, params).list(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Parameters} of indexed parameters + * @return a {@link List} containing all results, without paging + * @see #list(String, Parameters) + * @see #list(String, Sort, Object...) + * @see #list(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public default CompletionStage> list(String query, Sort sort, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a BSON query. + * This method is a shortcut for find(query).list(). + * + * @param query a {@link Document} query + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public default ReactivePanacheQuery list(Document query) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a a BSON query and a BSON sort. + * This method is a shortcut for find(query, sort).list(). + * + * @param query a {@link Document} query + * @param sort the {@link Document} sort + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public default ReactivePanacheQuery list(Document query, Document sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type. + * This method is a shortcut for findAll().list(). + * + * @return a {@link List} containing all results, without paging + * @see #listAll(Sort) + * @see #findAll() + * @see #streamAll() + */ + @GenerateBridge + public default CompletionStage> listAll() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type, in the given order. + * This method is a shortcut for findAll(sort).list(). + * + * @param sort the sort order to use + * @return a {@link List} containing all results, without paging + * @see #listAll() + * @see #findAll(Sort) + * @see #streamAll(Sort) + */ + @GenerateBridge + public default CompletionStage> listAll(Sort sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for find(query, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params optional sequence of indexed parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Sort, Object...) + * @see #stream(String, Map) + * @see #stream(String, Parameters) + * @see #find(String, Object...) + * @see #list(String, Object...) + */ + @GenerateBridge + public default Publisher stream(String query, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with optional indexed parameters. + * This method is a shortcut for find(query, sort, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Object...) + * @see #stream(String, Sort, Map) + * @see #stream(String, Sort, Parameters) + * @see #find(String, Sort, Object...) + * @see #list(String, Sort, Object...) + */ + @GenerateBridge + public default Publisher stream(String query, Sort sort, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for find(query, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Map} of named parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Sort, Map) + * @see #stream(String, Object...) + * @see #stream(String, Parameters) + * @see #find(String, Map) + * @see #list(String, Map) + */ + @GenerateBridge + public default Publisher stream(String query, Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with named parameters. + * This method is a shortcut for find(query, sort, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Map} of indexed parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Map) + * @see #stream(String, Sort, Object...) + * @see #stream(String, Sort, Parameters) + * @see #find(String, Sort, Map) + * @see #list(String, Sort, Map) + */ + @GenerateBridge + public default Publisher stream(String query, Sort sort, Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for find(query, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Parameters} of named parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Sort, Parameters) + * @see #stream(String, Object...) + * @see #stream(String, Map) + * @see #find(String, Parameters) + * @see #list(String, Parameters) + */ + @GenerateBridge + public default Publisher stream(String query, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities matching a query and the given sort options, with named parameters. + * This method is a shortcut for find(query, sort, params).stream(). + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param sort the sort strategy to use + * @param params {@link Parameters} of indexed parameters + * @return a {@link Stream} containing all results, without paging + * @see #stream(String, Parameters) + * @see #stream(String, Sort, Object...) + * @see #stream(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + */ + @GenerateBridge + public default Publisher stream(String query, Sort sort, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a BSON query. + * This method is a shortcut for find(query).stream(). + * + * @param query a {@link Document} query + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public default Publisher stream(Document query) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find entities using a a BSON query and a BSON sort. + * This method is a shortcut for find(query, sort).stream(). + * + * @param query a {@link Document} query + * @param sort the {@link Document} sort + * @return a new {@link ReactivePanacheQuery} instance for the given query + * @see #find(String, Parameters) + * @see #find(String, Sort, Map) + * @see #find(String, Sort, Parameters) + * @see #list(String, Sort, Parameters) + * @see #stream(String, Sort, Parameters) + */ + @GenerateBridge + public default Publisher stream(Document query, Document sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type. + * This method is a shortcut for findAll().stream(). + * + * @return a {@link Stream} containing all results, without paging + * @see #streamAll(Sort) + * @see #findAll() + * @see #listAll() + */ + @GenerateBridge + public default Publisher streamAll(Sort sort) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Find all entities of this type, in the given order. + * This method is a shortcut for findAll(sort).stream(). + * + * @return a {@link Stream} containing all results, without paging + * @see #streamAll() + * @see #findAll(Sort) + * @see #listAll(Sort) + */ + @GenerateBridge + public default Publisher streamAll() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Counts the number of this type of entity in the database. + * + * @return the number of this type of entity in the database. + * @see #count(String, Object...) + * @see #count(String, Map) + * @see #count(String, Parameters) + */ + @GenerateBridge + public default CompletionStage count() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Counts the number of this type of entity matching the given query, with optional indexed parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params optional sequence of indexed parameters + * @return the number of entities counted. + * @see #count() + * @see #count(String, Map) + * @see #count(String, Parameters) + */ + @GenerateBridge + public default CompletionStage count(String query, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Counts the number of this type of entity matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Map} of named parameters + * @return the number of entities counted. + * @see #count() + * @see #count(String, Object...) + * @see #count(String, Parameters) + */ + @GenerateBridge + public default CompletionStage count(String query, Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Counts the number of this type of entity matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Parameters} of named parameters + * @return the number of entities counted. + * @see #count() + * @see #count(String, Object...) + * @see #count(String, Map) + */ + @GenerateBridge + public default CompletionStage count(String query, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Counts the number of this type of entity matching the given query + * + * @param query a {@link Document} query + * @return he number of entities counted. + * @see #count() + * @see #count(String, Object...) + * @see #count(String, Map) + */ + @GenerateBridge + public default CompletionStage count(Document query) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Delete all entities of this type from the database. + * + * @return the number of entities deleted. + * @see #delete(String, Object...) + * @see #delete(String, Map) + * @see #delete(String, Parameters) + */ + @GenerateBridge + public default CompletionStage deleteAll() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Delete all entities of this type matching the given query, with optional indexed parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params optional sequence of indexed parameters + * @return the number of entities deleted. + * @see #deleteAll() + * @see #delete(String, Map) + * @see #delete(String, Parameters) + */ + @GenerateBridge + public default CompletionStage delete(String query, Object... params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Map} of named parameters + * @return the number of entities deleted. + * @see #deleteAll() + * @see #delete(String, Object...) + * @see #delete(String, Parameters) + */ + @GenerateBridge + public default CompletionStage delete(String query, Map params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a {@link io.quarkus.mongodb.panache query string} + * @param params {@link Parameters} of named parameters + * @return the number of entities deleted. + * @see #deleteAll() + * @see #delete(String, Object...) + * @see #delete(String, Map) + */ + @GenerateBridge + public default CompletionStage delete(String query, Parameters params) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Delete all entities of this type matching the given query + * + * @param query a {@link Document} query + * @return he number of entities counted. + * @see #count() + * @see #count(String, Object...) + * @see #count(String, Map) + */ + @GenerateBridge + public default CompletionStage delete(Document query) { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see #persist(Object) + * @see #persist(Stream) + * @see #persist(Object,Object...) + */ + public default CompletionStage persist(Iterable entities) { + return ReactiveMongoOperations.persist(entities); + } + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see #persist(Object) + * @see #persist(Iterable) + * @see #persist(Object,Object...) + */ + public default CompletionStage persist(Stream entities) { + return ReactiveMongoOperations.persist(entities); + } + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see #persist(Object) + * @see #persist(Stream) + * @see #persist(Iterable) + */ + public default CompletionStage persist(Entity firstEntity, @SuppressWarnings("unchecked") Entity... entities) { + return ReactiveMongoOperations.persist(firstEntity, entities); + } + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see #update(Object) + * @see #update(Stream) + * @see #update(Object,Object...) + */ + public default CompletionStage update(Iterable entities) { + return ReactiveMongoOperations.update(entities); + } + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see #update(Object) + * @see #update(Iterable) + * @see #update(Object,Object...) + */ + public default CompletionStage update(Stream entities) { + return ReactiveMongoOperations.update(entities); + } + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see #update(Object) + * @see #update(Stream) + * @see #update(Iterable) + */ + public default CompletionStage update(Entity firstEntity, @SuppressWarnings("unchecked") Entity... entities) { + return ReactiveMongoOperations.update(firstEntity, entities); + } + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see #persistOrUpdate(Object) + * @see #persistOrUpdate(Stream) + * @see #persistOrUpdate(Object,Object...) + */ + public default CompletionStage persistOrUpdate(Iterable entities) { + return ReactiveMongoOperations.persistOrUpdate(entities); + } + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see #persistOrUpdate(Object) + * @see #persistOrUpdate(Iterable) + * @see #persistOrUpdate(Object,Object...) + */ + public default CompletionStage persistOrUpdate(Stream entities) { + return ReactiveMongoOperations.persistOrUpdate(entities); + } + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see #update(Object) + * @see #update(Stream) + * @see #update(Iterable) + */ + public default CompletionStage persistOrUpdate(Entity firstEntity, + @SuppressWarnings("unchecked") Entity... entities) { + return ReactiveMongoOperations.persistOrUpdate(firstEntity, entities); + } + + /** + * Allow to access the underlying Mongo Collection + */ + @GenerateBridge + public default ReactiveMongoCollection mongoCollection() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Allow to access the underlying Mongo Database. + */ + @GenerateBridge + public default ReactiveMongoDatabase mongoDatabase() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheQuery.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheQuery.java new file mode 100644 index 0000000000000..5cf719da92d34 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/ReactivePanacheQuery.java @@ -0,0 +1,179 @@ +package io.quarkus.mongodb.panache.axle; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletionStage; +import java.util.stream.Stream; + +import org.reactivestreams.Publisher; + +import io.quarkus.panache.common.Page; + +/** + * Interface representing an entity query, which abstracts the use of paging, getting the number of results, and + * operating on {@link List} or {@link Stream}. + * + * Instances of this interface cannot mutate the query itself or its parameters: only paging information can be + * modified, and instances of this interface can be reused to obtain multiple pages of results. + * + * @param The entity type being queried + */ +public interface ReactivePanacheQuery { + + // Builder + + /** + * Sets the current page. + * + * @param page the new page + * @return this query, modified + * @see #page(int, int) + * @see #page() + */ + public ReactivePanacheQuery page(Page page); + + /** + * Sets the current page. + * + * @param pageIndex the page index + * @param pageSize the page size + * @return this query, modified + * @see #page(Page) + * @see #page() + */ + public ReactivePanacheQuery page(int pageIndex, int pageSize); + + /** + * Sets the current page to the next page + * + * @return this query, modified + * @see #previousPage() + */ + public ReactivePanacheQuery nextPage(); + + /** + * Sets the current page to the previous page (or the first page if there is no previous page) + * + * @return this query, modified + * @see #nextPage() + */ + public ReactivePanacheQuery previousPage(); + + /** + * Sets the current page to the first page + * + * @return this query, modified + * @see #lastPage() + */ + public ReactivePanacheQuery firstPage(); + + /** + * Sets the current page to the last page. This will cause reading of the entity count. + * + * @return this query, modified + * @see #firstPage() + * @see #count() + */ + public CompletionStage> lastPage(); + + /** + * Returns true if there is another page to read after the current one. + * This will cause reading of the entity count. + * + * @return true if there is another page to read + * @see #hasPreviousPage() + * @see #count() + */ + public CompletionStage hasNextPage(); + + /** + * Returns true if there is a page to read before the current one. + * + * @return true if there is a previous page to read + * @see #hasNextPage() + */ + public boolean hasPreviousPage(); + + /** + * Returns the total number of pages to be read using the current page size. + * This will cause reading of the entity count. + * + * @return the total number of pages to be read using the current page size. + */ + public CompletionStage pageCount(); + + /** + * Returns the current page. + * + * @return the current page + * @see #page(Page) + * @see #page(int,int) + */ + public Page page(); + + // Results + + /** + * Reads and caches the total number of entities this query operates on. This causes a database + * query with SELECT COUNT(*) and a query equivalent to the current query, minus + * ordering. + * + * @return the total number of entities this query operates on, cached. + */ + public CompletionStage count(); + + /** + * Returns the current page of results as a {@link List}. + * + * @return the current page of results as a {@link List}. + * @see #page(Page) + * @see #page() + */ + public CompletionStage> list(); + + /** + * Returns the current page of results as a {@link Stream}. + * + * @return the current page of results as a {@link Stream}. + * @see #list() + * @see #page(Page) + * @see #page() + */ + public Publisher stream(); + + /** + * Returns the first result of the current page index. This ignores the current page size to fetch + * a single result. + * + * @return the first result of the current page index, or null if there are no results. + * @see #singleResult() + */ + public CompletionStage firstResult(); + + /** + * Returns the first result of the current page index. This ignores the current page size to fetch + * a single result. + * + * @return if found, an optional containing the entity, else Optional.empty(). + * @see #singleResultOptional() + */ + public CompletionStage> firstResultOptional(); + + /** + * Executes this query for the current page and return a single result. + * + * @return the single result. + * @throws io.quarkus.panache.common.exception.PanacheQueryException if there are more than one result. + * @see #firstResult() + */ + public CompletionStage singleResult(); + + /** + * Executes this query for the current page and return a single result. + * + * @return if found, an optional containing the entity, else Optional.empty(). + * @throws io.quarkus.panache.common.exception.PanacheQueryException if there are more than one result. + * @see #firstResultOptional() + */ + public CompletionStage> singleResultOptional(); +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/runtime/ReactiveMongoOperations.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/runtime/ReactiveMongoOperations.java new file mode 100644 index 0000000000000..922954d92ecd3 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/runtime/ReactiveMongoOperations.java @@ -0,0 +1,577 @@ +package io.quarkus.mongodb.panache.axle.runtime; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.bson.BsonDocument; +import org.bson.BsonDocumentWriter; +import org.bson.BsonValue; +import org.bson.Document; +import org.bson.codecs.Codec; +import org.bson.codecs.EncoderContext; +import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.context.ThreadContext; +import org.jboss.logging.Logger; +import org.reactivestreams.Publisher; + +import com.mongodb.client.model.InsertOneModel; +import com.mongodb.client.model.ReplaceOneModel; +import com.mongodb.client.model.ReplaceOptions; +import com.mongodb.client.model.UpdateOptions; +import com.mongodb.client.model.WriteModel; + +import io.quarkus.arc.Arc; +import io.quarkus.mongodb.ReactiveMongoClient; +import io.quarkus.mongodb.ReactiveMongoCollection; +import io.quarkus.mongodb.ReactiveMongoDatabase; +import io.quarkus.mongodb.panache.MongoEntity; +import io.quarkus.mongodb.panache.axle.ReactivePanacheQuery; +import io.quarkus.mongodb.panache.binder.NativeQueryBinder; +import io.quarkus.mongodb.panache.binder.PanacheQlQueryBinder; +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; + +public class ReactiveMongoOperations { + private static final Logger LOGGER = Logger.getLogger(ReactiveMongoOperations.class); + public static final String ID = "_id"; + public static final String MONGODB_DATABASE = "quarkus.mongodb.database"; + // + // Instance methods + + public static CompletionStage persist(Object entity) { + ReactiveMongoCollection collection = mongoCollection(entity); + return persist(collection, entity); + } + + public static CompletionStage persist(Iterable entities) { + // not all iterables are re-traversal, so we traverse it once for copying inside a list + List objects = new ArrayList<>(); + for (Object entity : entities) { + objects.add(entity); + } + + if (objects.size() > 0) { + // get the first entity to be able to retrieve the collection with it + Object firstEntity = objects.get(0); + ReactiveMongoCollection collection = mongoCollection(firstEntity); + return persist(collection, objects); + } + return nullFuture(); + } + + public static CompletionStage persist(Object firstEntity, Object... entities) { + ReactiveMongoCollection collection = mongoCollection(firstEntity); + if (entities == null || entities.length == 0) { + return persist(collection, firstEntity); + } else { + List entityList = new ArrayList<>(); + entityList.add(firstEntity); + entityList.addAll(Arrays.asList(entities)); + return persist(collection, entityList); + } + } + + public static CompletionStage persist(Stream entities) { + List objects = entities.collect(Collectors.toList()); + if (objects.size() > 0) { + // get the first entity to be able to retrieve the collection with it + Object firstEntity = objects.get(0); + ReactiveMongoCollection collection = mongoCollection(firstEntity); + return persist(collection, objects); + } + return nullFuture(); + } + + public static CompletionStage update(Object entity) { + ReactiveMongoCollection collection = mongoCollection(entity); + return update(collection, entity); + } + + public static CompletionStage update(Iterable entities) { + // not all iterables are re-traversal, so we traverse it once for copying inside a list + List objects = new ArrayList<>(); + for (Object entity : entities) { + objects.add(entity); + } + + if (objects.size() > 0) { + // get the first entity to be able to retrieve the collection with it + Object firstEntity = objects.get(0); + ReactiveMongoCollection collection = mongoCollection(firstEntity); + return update(collection, objects); + } + return nullFuture(); + } + + public static CompletionStage update(Object firstEntity, Object... entities) { + ReactiveMongoCollection collection = mongoCollection(firstEntity); + if (entities == null || entities.length == 0) { + return update(collection, firstEntity); + } else { + List entityList = new ArrayList<>(); + entityList.add(firstEntity); + entityList.addAll(Arrays.asList(entities)); + return update(collection, entityList); + } + } + + public static CompletionStage update(Stream entities) { + List objects = entities.collect(Collectors.toList()); + if (objects.size() > 0) { + // get the first entity to be able to retrieve the collection with it + Object firstEntity = objects.get(0); + ReactiveMongoCollection collection = mongoCollection(firstEntity); + return update(collection, objects); + } + return nullFuture(); + } + + public static CompletionStage persistOrUpdate(Object entity) { + ReactiveMongoCollection collection = mongoCollection(entity); + return persistOrUpdate(collection, entity); + } + + public static CompletionStage persistOrUpdate(Iterable entities) { + // not all iterables are re-traversal, so we traverse it once for copying inside a list + List objects = new ArrayList<>(); + for (Object entity : entities) { + objects.add(entity); + } + + if (objects.size() > 0) { + // get the first entity to be able to retrieve the collection with it + Object firstEntity = objects.get(0); + ReactiveMongoCollection collection = mongoCollection(firstEntity); + return persistOrUpdate(collection, objects); + } + return nullFuture(); + } + + public static CompletionStage persistOrUpdate(Object firstEntity, Object... entities) { + ReactiveMongoCollection collection = mongoCollection(firstEntity); + if (entities == null || entities.length == 0) { + return persistOrUpdate(collection, firstEntity); + } else { + List entityList = new ArrayList<>(); + entityList.add(firstEntity); + entityList.addAll(Arrays.asList(entities)); + return persistOrUpdate(collection, entityList); + } + } + + public static CompletionStage persistOrUpdate(Stream entities) { + List objects = entities.collect(Collectors.toList()); + if (objects.size() > 0) { + // get the first entity to be able to retrieve the collection with it + Object firstEntity = objects.get(0); + ReactiveMongoCollection collection = mongoCollection(firstEntity); + return persistOrUpdate(collection, objects); + } + return nullFuture(); + } + + public static CompletionStage delete(Object entity) { + ReactiveMongoCollection collection = mongoCollection(entity); + BsonDocument document = getBsonDocument(collection, entity); + BsonValue id = document.get(ID); + BsonDocument query = new BsonDocument().append(ID, id); + return collection.deleteOne(query).thenApply(r -> null); + } + + public static ReactiveMongoCollection mongoCollection(Class entityClass) { + MongoEntity mongoEntity = entityClass.getAnnotation(MongoEntity.class); + ReactiveMongoDatabase database = mongoDatabase(mongoEntity); + if (mongoEntity != null && !mongoEntity.collection().isEmpty()) { + return database.getCollection(mongoEntity.collection(), entityClass); + } + return database.getCollection(entityClass.getSimpleName(), entityClass); + } + + public static ReactiveMongoDatabase mongoDatabase(Class entityClass) { + MongoEntity mongoEntity = entityClass.getAnnotation(MongoEntity.class); + return mongoDatabase(mongoEntity); + } + + // + // Private stuff + + public static CompletableFuture nullFuture() { + return completedFuture(null); + } + + public static CompletableFuture completedFuture(U value) { + ThreadContext threadContext = Arc.container().instance(ThreadContext.class).get(); + return threadContext.withContextCapture(CompletableFuture.completedFuture(value)); + } + + private static CompletionStage persist(ReactiveMongoCollection collection, Object entity) { + return collection.insertOne(entity); + } + + private static CompletionStage persist(ReactiveMongoCollection collection, List entities) { + return collection.insertMany(entities); + } + + private static CompletionStage update(ReactiveMongoCollection collection, Object entity) { + //we transform the entity as a document first + BsonDocument document = getBsonDocument(collection, entity); + + //then we get its id field and create a new Document with only this one that will be our replace query + BsonValue id = document.get(ID); + BsonDocument query = new BsonDocument().append(ID, id); + return collection.replaceOne(query, entity).thenApply(u -> null); + } + + private static CompletionStage update(ReactiveMongoCollection collection, List entities) { + CompletionStage ret = nullFuture(); + for (Object entity : entities) { + ret.thenCompose(v -> update(collection, entity)); + } + return ret.thenApply(v -> null); + } + + private static CompletionStage persistOrUpdate(ReactiveMongoCollection collection, Object entity) { + //we transform the entity as a document first + BsonDocument document = getBsonDocument(collection, entity); + + //then we get its id field and create a new Document with only this one that will be our replace query + BsonValue id = document.get(ID); + if (id == null) { + //insert with autogenerated ID + return collection.insertOne(entity); + } else { + //insert with user provided ID or update + BsonDocument query = new BsonDocument().append(ID, id); + return collection.replaceOne(query, entity, ReplaceOptions.createReplaceOptions(new UpdateOptions().upsert(true))) + .thenApply(u -> null); + } + } + + private static CompletionStage persistOrUpdate(ReactiveMongoCollection collection, List entities) { + //this will be an ordered bulk: it's less performant than a unordered one but will fail at the first failed write + List bulk = new ArrayList<>(); + for (Object entity : entities) { + //we transform the entity as a document first + BsonDocument document = getBsonDocument(collection, entity); + + //then we get its id field and create a new Document with only this one that will be our replace query + BsonValue id = document.get(ID); + if (id == null) { + //insert with autogenerated ID + bulk.add(new InsertOneModel(entity)); + } else { + //insert with user provided ID or update + BsonDocument query = new BsonDocument().append(ID, id); + bulk.add(new ReplaceOneModel(query, entity, + ReplaceOptions.createReplaceOptions(new UpdateOptions().upsert(true)))); + } + } + + return collection.bulkWrite(bulk).thenApply(b -> null); + } + + private static BsonDocument getBsonDocument(ReactiveMongoCollection collection, Object entity) { + BsonDocument document = new BsonDocument(); + Codec codec = collection.getCodecRegistry().get(entity.getClass()); + codec.encode(new BsonDocumentWriter(document), entity, EncoderContext.builder().build()); + return document; + } + + private static ReactiveMongoCollection mongoCollection(Object entity) { + Class entityClass = entity.getClass(); + return mongoCollection(entityClass); + } + + private static ReactiveMongoDatabase mongoDatabase(MongoEntity entity) { + ReactiveMongoClient mongoClient = Arc.container().instance(ReactiveMongoClient.class).get(); + if (entity != null && !entity.database().isEmpty()) { + return mongoClient.getDatabase(entity.database()); + } + String databaseName = ConfigProvider.getConfig() + .getValue(MONGODB_DATABASE, String.class); + return mongoClient.getDatabase(databaseName); + } + + // + // Queries + + public static CompletionStage findById(Class entityClass, Object id) { + CompletionStage optionalEntity = findByIdOptional(entityClass, id); + return optionalEntity.thenApply(optional -> optional.orElse(null)); + } + + public static CompletionStage findByIdOptional(Class entityClass, Object id) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.find(new Document(ID, id)).findFirst().run(); + } + + public static ReactivePanacheQuery find(Class entityClass, String query, Object... params) { + return find(entityClass, query, null, params); + } + + @SuppressWarnings("rawtypes") + public static ReactivePanacheQuery find(Class entityClass, String query, Sort sort, Object... params) { + String bindQuery = bindQuery(entityClass, query, params); + Document docQuery = Document.parse(bindQuery); + Document docSort = sortToDocument(sort); + ReactiveMongoCollection collection = mongoCollection(entityClass); + return new ReactivePanacheQueryImpl(collection, entityClass, docQuery, docSort); + } + + /** + * We should have a query like {'firstname': ?1, 'lastname': ?2} for native one + * and like firstname = ?1 for PanacheQL one. + */ + static String bindQuery(Class clazz, String query, Object[] params) { + String bindQuery = null; + + //determine the type of the query + if (query.charAt(0) == '{') { + //this is a native query + bindQuery = NativeQueryBinder.bindQuery(query, params); + } else { + //this is a PanacheQL query + bindQuery = PanacheQlQueryBinder.bindQuery(clazz, query, params); + } + + LOGGER.debug(bindQuery); + return bindQuery; + } + + /** + * We should have a query like {'firstname': :firstname, 'lastname': :lastname} for native one + * and like firstname = :firstname and lastname = :lastname for PanacheQL one. + */ + static String bindQuery(Class clazz, String query, Map params) { + String bindQuery = null; + + //determine the type of the query + if (query.charAt(0) == '{') { + //this is a native query + bindQuery = NativeQueryBinder.bindQuery(query, params); + } else { + //this is a PanacheQL query + bindQuery = PanacheQlQueryBinder.bindQuery(clazz, query, params); + } + + LOGGER.debug(bindQuery); + return bindQuery; + } + + public static ReactivePanacheQuery find(Class entityClass, String query, Map params) { + return find(entityClass, query, null, params); + } + + @SuppressWarnings("rawtypes") + public static ReactivePanacheQuery find(Class entityClass, String query, Sort sort, Map params) { + String bindQuery = bindQuery(entityClass, query, params); + Document docQuery = Document.parse(bindQuery); + Document docSort = sortToDocument(sort); + ReactiveMongoCollection collection = mongoCollection(entityClass); + return new ReactivePanacheQueryImpl(collection, entityClass, docQuery, docSort); + } + + public static ReactivePanacheQuery find(Class entityClass, String query, Parameters params) { + return find(entityClass, query, null, params.map()); + } + + public static ReactivePanacheQuery find(Class entityClass, String query, Sort sort, Parameters params) { + return find(entityClass, query, sort, params.map()); + } + + @SuppressWarnings("rawtypes") + public static ReactivePanacheQuery find(Class entityClass, Document query, Sort sort) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + Document sortDoc = sortToDocument(sort); + return new ReactivePanacheQueryImpl(collection, entityClass, query, sortDoc); + } + + public static ReactivePanacheQuery find(Class entityClass, Document query, Document sort) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return new ReactivePanacheQueryImpl(collection, entityClass, query, sort); + } + + public static ReactivePanacheQuery find(Class entityClass, Document query) { + return find(entityClass, query, (Document) null); + } + + public static CompletionStage> list(Class entityClass, String query, Object... params) { + return (CompletionStage) find(entityClass, query, params).list(); + } + + public static CompletionStage> list(Class entityClass, String query, Sort sort, Object... params) { + return (CompletionStage) find(entityClass, query, sort, params).list(); + } + + public static CompletionStage> list(Class entityClass, String query, Map params) { + return (CompletionStage) find(entityClass, query, params).list(); + } + + public static CompletionStage> list(Class entityClass, String query, Sort sort, Map params) { + return (CompletionStage) find(entityClass, query, sort, params).list(); + } + + public static CompletionStage> list(Class entityClass, String query, Parameters params) { + return (CompletionStage) find(entityClass, query, params).list(); + } + + public static CompletionStage> list(Class entityClass, String query, Sort sort, Parameters params) { + return (CompletionStage) find(entityClass, query, sort, params).list(); + } + + //specific Mongo query + public static CompletionStage> list(Class entityClass, Document query) { + return (CompletionStage) find(entityClass, query).list(); + } + + //specific Mongo query + public static CompletionStage> list(Class entityClass, Document query, Document sort) { + return (CompletionStage) find(entityClass, query, sort).list(); + } + + public static Publisher stream(Class entityClass, String query, Object... params) { + return find(entityClass, query, params).stream(); + } + + public static Publisher stream(Class entityClass, String query, Sort sort, Object... params) { + return find(entityClass, query, sort, params).stream(); + } + + public static Publisher stream(Class entityClass, String query, Map params) { + return find(entityClass, query, params).stream(); + } + + public static Publisher stream(Class entityClass, String query, Sort sort, Map params) { + return find(entityClass, query, sort, params).stream(); + } + + public static Publisher stream(Class entityClass, String query, Parameters params) { + return find(entityClass, query, params).stream(); + } + + public static Publisher stream(Class entityClass, String query, Sort sort, Parameters params) { + return find(entityClass, query, sort, params).stream(); + } + + //specific Mongo query + public static Publisher stream(Class entityClass, Document query) { + return find(entityClass, query).stream(); + } + + //specific Mongo query + public static Publisher stream(Class entityClass, Document query, Document sort) { + return find(entityClass, query, sort).stream(); + } + + @SuppressWarnings("rawtypes") + public static ReactivePanacheQuery findAll(Class entityClass) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return new ReactivePanacheQueryImpl(collection, entityClass, null, null); + } + + @SuppressWarnings("rawtypes") + public static ReactivePanacheQuery findAll(Class entityClass, Sort sort) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + Document sortDoc = sortToDocument(sort); + return new ReactivePanacheQueryImpl(collection, entityClass, null, sortDoc); + } + + private static Document sortToDocument(Sort sort) { + if (sort == null) { + return null; + } + + Document sortDoc = new Document(); + for (Sort.Column col : sort.getColumns()) { + sortDoc.append(col.getName(), col.getDirection() == Sort.Direction.Ascending ? 1 : -1); + } + return sortDoc; + } + + public static CompletionStage> listAll(Class entityClass) { + return (CompletionStage) findAll(entityClass).list(); + } + + public static CompletionStage> listAll(Class entityClass, Sort sort) { + return (CompletionStage) findAll(entityClass, sort).list(); + } + + public static Publisher streamAll(Class entityClass) { + return findAll(entityClass).stream(); + } + + public static Publisher streamAll(Class entityClass, Sort sort) { + return findAll(entityClass, sort).stream(); + } + + public static CompletionStage count(Class entityClass) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.countDocuments(); + } + + public static CompletionStage count(Class entityClass, String query, Object... params) { + String bindQuery = bindQuery(entityClass, query, params); + Document docQuery = Document.parse(bindQuery); + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.countDocuments(docQuery); + } + + public static CompletionStage count(Class entityClass, String query, Map params) { + String bindQuery = bindQuery(entityClass, query, params); + Document docQuery = Document.parse(bindQuery); + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.countDocuments(docQuery); + } + + public static CompletionStage count(Class entityClass, String query, Parameters params) { + return count(entityClass, query, params.map()); + } + + //specific Mongo query + public static CompletionStage count(Class entityClass, Document query) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.countDocuments(query); + } + + public static CompletionStage deleteAll(Class entityClass) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.deleteMany(new Document()).thenApply(deleteResult -> deleteResult.getDeletedCount()); + } + + public static CompletionStage delete(Class entityClass, String query, Object... params) { + String bindQuery = bindQuery(entityClass, query, params); + Document docQuery = Document.parse(bindQuery); + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.deleteMany(docQuery).thenApply(deleteResult -> deleteResult.getDeletedCount()); + } + + public static CompletionStage delete(Class entityClass, String query, Map params) { + String bindQuery = bindQuery(entityClass, query, params); + Document docQuery = Document.parse(bindQuery); + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.deleteMany(docQuery).thenApply(deleteResult -> deleteResult.getDeletedCount()); + } + + public static CompletionStage delete(Class entityClass, String query, Parameters params) { + return delete(entityClass, query, params.map()); + } + + //specific Mongo query + public static CompletionStage delete(Class entityClass, Document query) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.deleteMany(query).thenApply(deleteResult -> deleteResult.getDeletedCount()); + } + + public static IllegalStateException implementationInjectionMissing() { + return new IllegalStateException( + "This method is normally automatically overridden in subclasses"); + } + +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/runtime/ReactivePanacheQueryImpl.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/runtime/ReactivePanacheQueryImpl.java new file mode 100644 index 0000000000000..c91beac94405c --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/axle/runtime/ReactivePanacheQueryImpl.java @@ -0,0 +1,171 @@ +package io.quarkus.mongodb.panache.axle.runtime; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletionStage; + +import org.bson.Document; +import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder; +import org.reactivestreams.Publisher; + +import io.quarkus.mongodb.FindOptions; +import io.quarkus.mongodb.ReactiveMongoCollection; +import io.quarkus.mongodb.panache.axle.ReactivePanacheQuery; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.exception.PanacheQueryException; + +public class ReactivePanacheQueryImpl implements ReactivePanacheQuery { + private ReactiveMongoCollection collection; + private Class entityClass; + private Document mongoQuery; + private Document sort; + + /* + * We store the pageSize and apply it for each request because getFirstResult() + * sets the page size to 1 + */ + private Page page; + private CompletionStage count; + + ReactivePanacheQueryImpl(ReactiveMongoCollection collection, Class entityClass, + Document mongoQuery, + Document sort) { + this.collection = collection; + this.entityClass = entityClass; + this.mongoQuery = mongoQuery; + this.sort = sort; + page = new Page(0, Integer.MAX_VALUE); + } + + // Builder + + @Override + @SuppressWarnings("unchecked") + public ReactivePanacheQuery page(Page page) { + this.page = page; + return (ReactivePanacheQuery) this; + } + + @Override + public ReactivePanacheQuery page(int pageIndex, int pageSize) { + return page(Page.of(pageIndex, pageSize)); + } + + @Override + public ReactivePanacheQuery nextPage() { + return page(page.next()); + } + + @Override + public ReactivePanacheQuery previousPage() { + return page(page.previous()); + } + + @Override + public ReactivePanacheQuery firstPage() { + return page(page.first()); + } + + @Override + public CompletionStage> lastPage() { + return pageCount().thenApply(pageCount -> { + return page(page.index(pageCount - 1)); + }); + } + + @Override + public CompletionStage hasNextPage() { + return pageCount().thenApply(pageCount -> { + return page.index < (pageCount - 1); + }); + } + + @Override + public boolean hasPreviousPage() { + return page.index > 0; + } + + @Override + public CompletionStage pageCount() { + return count().thenApply(count -> { + if (count == 0) + return 1; // a single page of zero results + return (int) Math.ceil((double) count / (double) page.size); + }); + } + + @Override + public Page page() { + return page; + } + + // Results + + @Override + @SuppressWarnings("unchecked") + public CompletionStage count() { + if (count == null) { + count = collection.countDocuments(mongoQuery); + } + return count; + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage> list() { + FindOptions options = new FindOptions(); + options.sort(sort).skip(page.index).limit(page.size); + PublisherBuilder results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + return results.toList().run(); + } + + @Override + @SuppressWarnings("unchecked") + public Publisher stream() { + FindOptions options = new FindOptions(); + options.sort(sort).skip(page.index).limit(page.size); + return mongoQuery == null ? collection.find(options).buildRs() : collection.find(mongoQuery, options).buildRs(); + } + + @Override + public CompletionStage firstResult() { + CompletionStage> optionalEntity = firstResultOptional(); + return optionalEntity.thenApply(optional -> optional.orElse(null)); + } + + @Override + public CompletionStage> firstResultOptional() { + FindOptions options = new FindOptions(); + options.sort(sort).skip(page.index).limit(1); + return mongoQuery == null ? collection.find(options).findFirst().run() + : collection.find(mongoQuery, options).findFirst().run(); + } + + @Override + @SuppressWarnings("unchecked") + public CompletionStage singleResult() { + FindOptions options = new FindOptions(); + options.sort(sort).skip(page.index).limit(2); + PublisherBuilder results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + return results.toList().run().thenApply(list -> { + if (list.size() == 0 || list.size() > 1) { + throw new PanacheQueryException("There should be only one result"); + } else { + return list.get(0); + } + }); + } + + @Override + public CompletionStage> singleResultOptional() { + FindOptions options = new FindOptions(); + options.sort(sort).skip(page.index).limit(2); + PublisherBuilder results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + return results.toList().run().thenApply(list -> { + if (list.size() == 2) { + throw new PanacheQueryException("There should be no more than one result"); + } + return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); + }); + } +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/CommonQueryBinder.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java similarity index 97% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/CommonQueryBinder.java rename to extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java index f63874a48b44f..279f5ac957180 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/CommonQueryBinder.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java @@ -1,4 +1,4 @@ -package io.quarkus.mongodb.panache.runtime; +package io.quarkus.mongodb.panache.binder; import java.text.SimpleDateFormat; import java.time.Instant; diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoParserVisitor.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/MongoParserVisitor.java similarity index 98% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoParserVisitor.java rename to extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/MongoParserVisitor.java index 1c093b86611b6..a21722ddeddf8 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoParserVisitor.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/MongoParserVisitor.java @@ -1,4 +1,4 @@ -package io.quarkus.mongodb.panache.runtime; +package io.quarkus.mongodb.panache.binder; import java.util.Map; diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/NativeQueryBinder.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/NativeQueryBinder.java similarity index 90% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/NativeQueryBinder.java rename to extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/NativeQueryBinder.java index 588c8b96f356b..fdff302e77b4d 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/NativeQueryBinder.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/NativeQueryBinder.java @@ -1,8 +1,8 @@ -package io.quarkus.mongodb.panache.runtime; +package io.quarkus.mongodb.panache.binder; import java.util.Map; -class NativeQueryBinder { +public class NativeQueryBinder { public static String bindQuery(String query, Object[] params) { String bindQuery = query; diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheQlQueryBinder.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/PanacheQlQueryBinder.java similarity index 95% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheQlQueryBinder.java rename to extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/PanacheQlQueryBinder.java index 06d36c0ba6ad1..cfc21b2efb9a9 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheQlQueryBinder.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/PanacheQlQueryBinder.java @@ -1,4 +1,4 @@ -package io.quarkus.mongodb.panache.runtime; +package io.quarkus.mongodb.panache.binder; import java.util.HashMap; import java.util.Map; @@ -6,6 +6,7 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; +import io.quarkus.mongodb.panache.runtime.MongoPropertyUtil; import io.quarkus.panacheql.internal.HqlLexer; import io.quarkus.panacheql.internal.HqlParser; import io.quarkus.panacheql.internal.HqlParserBaseVisitor; diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java index a79618984bdd9..ec75118bc54d0 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java @@ -29,6 +29,8 @@ import io.quarkus.arc.Arc; import io.quarkus.mongodb.panache.MongoEntity; import io.quarkus.mongodb.panache.PanacheQuery; +import io.quarkus.mongodb.panache.binder.NativeQueryBinder; +import io.quarkus.mongodb.panache.binder.PanacheQlQueryBinder; import io.quarkus.panache.common.Parameters; import io.quarkus.panache.common.Sort; diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoPropertyUtil.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoPropertyUtil.java index 2b452f3f5f580..e020c3d086f1a 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoPropertyUtil.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoPropertyUtil.java @@ -7,13 +7,13 @@ import org.bson.codecs.pojo.annotations.BsonProperty; -final class MongoPropertyUtil { +public final class MongoPropertyUtil { private MongoPropertyUtil() { //prevent initialization } - static Map extractReplacementMap(Class clazz) { + public static Map extractReplacementMap(Class clazz) { //TODO cache the replacement map or pre-compute it during build (using reflection or jandex) Map replacementMap = new HashMap<>(); for (Field field : clazz.getDeclaredFields()) { diff --git a/integration-tests/mongodb-panache/pom.xml b/integration-tests/mongodb-panache/pom.xml index 2f889f6f7fec3..875eb656f078c 100755 --- a/integration-tests/mongodb-panache/pom.xml +++ b/integration-tests/mongodb-panache/pom.xml @@ -23,6 +23,10 @@ io.quarkus quarkus-mongodb-panache + + io.quarkus + quarkus-rest-client + io.quarkus diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookEntity.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookEntity.java new file mode 100644 index 0000000000000..fd21ac9f3fae0 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookEntity.java @@ -0,0 +1,81 @@ +package io.quarkus.it.mongodb.panache.axle.book; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import javax.json.bind.annotation.JsonbDateFormat; + +import org.bson.codecs.pojo.annotations.BsonIgnore; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import io.quarkus.it.mongodb.panache.book.BookDetail; +import io.quarkus.mongodb.panache.MongoEntity; +import io.quarkus.mongodb.panache.axle.ReactivePanacheMongoEntity; + +@MongoEntity(collection = "TheBookEntity") +public class ReactiveBookEntity extends ReactivePanacheMongoEntity { + @BsonProperty("bookTitle") + private String title; + private String author; + @BsonIgnore + private String transientDescription; + @JsonbDateFormat("yyyy-MM-dd") + private LocalDate creationDate; + + private List categories = new ArrayList<>(); + + private BookDetail details; + + public String getTitle() { + return title; + } + + public ReactiveBookEntity setTitle(String title) { + this.title = title; + return this; + } + + public String getAuthor() { + return author; + } + + public ReactiveBookEntity setAuthor(String author) { + this.author = author; + return this; + } + + public List getCategories() { + return categories; + } + + public ReactiveBookEntity setCategories(List categories) { + this.categories = categories; + return this; + } + + public BookDetail getDetails() { + return details; + } + + public ReactiveBookEntity setDetails(BookDetail details) { + this.details = details; + return this; + } + + public String getTransientDescription() { + return transientDescription; + } + + public void setTransientDescription(String transientDescription) { + this.transientDescription = transientDescription; + } + + public LocalDate getCreationDate() { + return creationDate; + } + + public void setCreationDate(LocalDate creationDate) { + this.creationDate = creationDate; + } +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookEntityResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookEntityResource.java new file mode 100644 index 0000000000000..58126393b2556 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookEntityResource.java @@ -0,0 +1,118 @@ +package io.quarkus.it.mongodb.panache.axle.book; + +import java.net.URI; +import java.time.LocalDate; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import javax.annotation.PostConstruct; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.bson.types.ObjectId; +import org.jboss.logging.Logger; +import org.jboss.resteasy.annotations.SseElementType; +import org.reactivestreams.Publisher; + +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; + +@Path("/axle/books/entity") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ReactiveBookEntityResource { + private static final Logger LOGGER = Logger.getLogger(ReactiveBookEntityResource.class); + + @PostConstruct + void init() { + String databaseName = ReactiveBookEntity.mongoDatabase().getName(); + String collectionName = ReactiveBookEntity.mongoCollection().getNamespace().getCollectionName(); + LOGGER.infov("Using BookEntity[database={0}, collection={1}]", databaseName, collectionName); + } + + @GET + public CompletionStage> getBooks(@QueryParam("sort") String sort) { + if (sort != null) { + return ReactiveBookEntity.listAll(Sort.ascending(sort)); + } + return ReactiveBookEntity.listAll(); + } + + @GET + @Path("/stream") + @Produces(MediaType.SERVER_SENT_EVENTS) + @SseElementType(MediaType.APPLICATION_JSON) + public Publisher streamBooks(@QueryParam("sort") String sort) { + if (sort != null) { + return ReactiveBookEntity.streamAll(Sort.ascending(sort)); + } + return ReactiveBookEntity.streamAll(); + } + + @POST + public CompletionStage addBook(ReactiveBookEntity book) { + return book.persist().thenApply(v -> { + //the ID is populated before sending it to the database + String id = book.id.toString(); + return Response.created(URI.create("/books/entity" + id)).build(); + }); + } + + @PUT + public CompletionStage updateBook(ReactiveBookEntity book) { + return book.update().thenApply(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public CompletionStage upsertBook(ReactiveBookEntity book) { + return book.persistOrUpdate().thenApply(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public CompletionStage deleteBook(@PathParam("id") String id) { + return ReactiveBookEntity.findById(new ObjectId(id)).thenCompose(book -> book.delete()); + } + + @GET + @Path("/{id}") + public CompletionStage getBook(@PathParam("id") String id) { + return ReactiveBookEntity.findById(new ObjectId(id)); + } + + @GET + @Path("/search/{author}") + public CompletionStage> getBooksByAuthor(@PathParam("author") String author) { + return ReactiveBookEntity.list("author", author); + } + + @GET + @Path("/search") + public CompletionStage search(@QueryParam("author") String author, @QueryParam("title") String title, + @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo) { + if (author != null) { + return ReactiveBookEntity.find("{'author': ?1,'bookTitle': ?2}", author, title).firstResult(); + } + + return ReactiveBookEntity + .find("{'creationDate': {$gte: ?1}, 'creationDate': {$lte: ?2}}", LocalDate.parse(dateFrom), + LocalDate.parse(dateTo)) + .firstResult(); + } + + @GET + @Path("/search2") + public CompletionStage search2(@QueryParam("author") String author, @QueryParam("title") String title, + @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo) { + if (author != null) { + return ReactiveBookEntity.find("{'author': :author,'bookTitle': :title}", + Parameters.with("author", author).and("title", title)).firstResult(); + } + + return ReactiveBookEntity.find("{'creationDate': {$gte: :dateFrom}, 'creationDate': {$lte: :dateTo}}", + Parameters.with("dateFrom", LocalDate.parse(dateFrom)).and("dateTo", LocalDate.parse(dateTo))).firstResult(); + } + +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookRepository.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookRepository.java new file mode 100644 index 0000000000000..5cc4143e066d4 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookRepository.java @@ -0,0 +1,10 @@ +package io.quarkus.it.mongodb.panache.axle.book; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.it.mongodb.panache.book.Book; +import io.quarkus.mongodb.panache.axle.ReactivePanacheMongoRepository; + +@ApplicationScoped +public class ReactiveBookRepository implements ReactivePanacheMongoRepository { +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookRepositoryResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookRepositoryResource.java new file mode 100644 index 0000000000000..005337e2e0558 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/book/ReactiveBookRepositoryResource.java @@ -0,0 +1,122 @@ +package io.quarkus.it.mongodb.panache.axle.book; + +import java.net.URI; +import java.time.LocalDate; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.bson.types.ObjectId; +import org.jboss.logging.Logger; +import org.jboss.resteasy.annotations.SseElementType; +import org.reactivestreams.Publisher; + +import io.quarkus.it.mongodb.panache.book.Book; +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; + +@Path("/axle/books/repository") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ReactiveBookRepositoryResource { + private static final Logger LOGGER = Logger.getLogger(ReactiveBookRepositoryResource.class); + @Inject + ReactiveBookRepository reactiveBookRepository; + + @PostConstruct + void init() { + String databaseName = reactiveBookRepository.mongoDatabase().getName(); + String collectionName = reactiveBookRepository.mongoCollection().getNamespace().getCollectionName(); + LOGGER.infov("Using BookRepository[database={0}, collection={1}]", databaseName, collectionName); + } + + @GET + public CompletionStage> getBooks(@QueryParam("sort") String sort) { + if (sort != null) { + return reactiveBookRepository.listAll(Sort.ascending(sort)); + } + return reactiveBookRepository.listAll(); + } + + @GET + @Path("/stream") + @Produces(MediaType.SERVER_SENT_EVENTS) + @SseElementType(MediaType.APPLICATION_JSON) + public Publisher streamBooks(@QueryParam("sort") String sort) { + if (sort != null) { + return reactiveBookRepository.streamAll(Sort.ascending(sort)); + } + return reactiveBookRepository.streamAll(); + } + + @POST + public CompletionStage addBook(Book book) { + return reactiveBookRepository.persist(book).thenApply(v -> { + //the ID is populated before sending it to the database + String id = book.getId().toString(); + return Response.created(URI.create("/books/entity" + id)).build(); + }); + } + + @PUT + public CompletionStage updateBook(Book book) { + return reactiveBookRepository.update(book).thenApply(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public CompletionStage upsertBook(Book book) { + return reactiveBookRepository.persistOrUpdate(book).thenApply(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public CompletionStage deleteBook(@PathParam("id") String id) { + return reactiveBookRepository.findById(new ObjectId(id)).thenCompose(book -> reactiveBookRepository.delete(book)); + } + + @GET + @Path("/{id}") + public CompletionStage getBook(@PathParam("id") String id) { + return reactiveBookRepository.findById(new ObjectId(id)); + } + + @GET + @Path("/search/{author}") + public CompletionStage> getBooksByAuthor(@PathParam("author") String author) { + return reactiveBookRepository.list("author", author); + } + + @GET + @Path("/search") + public CompletionStage search(@QueryParam("author") String author, @QueryParam("title") String title, + @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo) { + if (author != null) { + return reactiveBookRepository.find("{'author': ?1,'bookTitle': ?2}", author, title).firstResult(); + } + + return reactiveBookRepository + .find("{'creationDate': {$gte: ?1}, 'creationDate': {$lte: ?2}}", LocalDate.parse(dateFrom), + LocalDate.parse(dateTo)) + .firstResult(); + } + + @GET + @Path("/search2") + public CompletionStage search2(@QueryParam("author") String author, @QueryParam("title") String title, + @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo) { + if (author != null) { + return reactiveBookRepository.find("{'author': :author,'bookTitle': :title}", + Parameters.with("author", author).and("title", title)).firstResult(); + } + + return reactiveBookRepository.find("{'creationDate': {$gte: :dateFrom}, 'creationDate': {$lte: :dateTo}}", + Parameters.with("dateFrom", LocalDate.parse(dateFrom)).and("dateTo", LocalDate.parse(dateTo))).firstResult(); + } + +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonEntity.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonEntity.java new file mode 100644 index 0000000000000..c3b11d5064edf --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonEntity.java @@ -0,0 +1,12 @@ +package io.quarkus.it.mongodb.panache.axle.person; + +import org.bson.codecs.pojo.annotations.BsonId; + +import io.quarkus.mongodb.panache.axle.ReactivePanacheMongoEntityBase; + +public class ReactivePersonEntity extends ReactivePanacheMongoEntityBase { + @BsonId + public Long id; + public String firstname; + public String lastname; +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonEntityResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonEntityResource.java new file mode 100644 index 0000000000000..89a69aee56bc1 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonEntityResource.java @@ -0,0 +1,74 @@ +package io.quarkus.it.mongodb.panache.axle.person; + +import java.net.URI; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import io.quarkus.panache.common.Sort; + +@Path("/axle/persons/entity") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ReactivePersonEntityResource { + @GET + public CompletionStage> getPersons(@QueryParam("sort") String sort) { + if (sort != null) { + return ReactivePersonEntity.listAll(Sort.ascending(sort)); + } + return ReactivePersonEntity.listAll(); + } + + @POST + public CompletionStage addPerson(ReactivePersonEntity person) { + return person.persist().thenApply(v -> { + //the ID is populated before sending it to the database + String id = person.id.toString(); + return Response.created(URI.create("/persons/entity" + id)).build(); + }); + } + + @POST + @Path("/multiple") + public CompletionStage addPersons(List persons) { + return ReactivePersonEntity.persist(persons); + } + + @PUT + public CompletionStage updatePerson(ReactivePersonEntity person) { + return person.update().thenApply(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public CompletionStage upsertPerson(ReactivePersonEntity person) { + return person.persistOrUpdate().thenApply(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public CompletionStage deletePerson(@PathParam("id") String id) { + return ReactivePersonEntity.findById(Long.parseLong(id)).thenCompose(person -> person.delete()); + } + + @GET + @Path("/{id}") + public CompletionStage getPerson(@PathParam("id") String id) { + return ReactivePersonEntity.findById(Long.parseLong(id)); + } + + @GET + @Path("/count") + public CompletionStage countAll() { + return ReactivePersonEntity.count(); + } + + @DELETE + public CompletionStage deleteAll() { + return ReactivePersonEntity.deleteAll().thenAccept(l -> { + }); + } +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonRepository.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonRepository.java new file mode 100644 index 0000000000000..227b2af3e5a25 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonRepository.java @@ -0,0 +1,10 @@ +package io.quarkus.it.mongodb.panache.axle.person; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.it.mongodb.panache.person.Person; +import io.quarkus.mongodb.panache.axle.ReactivePanacheMongoRepositoryBase; + +@ApplicationScoped +public class ReactivePersonRepository implements ReactivePanacheMongoRepositoryBase { +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonRepositoryResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonRepositoryResource.java new file mode 100644 index 0000000000000..0a2359b2aac29 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/axle/person/ReactivePersonRepositoryResource.java @@ -0,0 +1,81 @@ +package io.quarkus.it.mongodb.panache.axle.person; + +import java.net.URI; +import java.util.List; +import java.util.concurrent.CompletionStage; + +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import io.quarkus.it.mongodb.panache.person.Person; +import io.quarkus.panache.common.Sort; + +@Path("/axle/persons/repository") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ReactivePersonRepositoryResource { + + @Inject + ReactivePersonRepository reactivePersonRepository; + + @GET + public CompletionStage> getPersons(@QueryParam("sort") String sort) { + if (sort != null) { + return reactivePersonRepository.listAll(Sort.ascending(sort)); + } + return reactivePersonRepository.listAll(); + } + + @POST + public CompletionStage addPerson(Person person) { + return reactivePersonRepository.persist(person).thenApply(v -> { + //the ID is populated before sending it to the database + String id = person.id.toString(); + return Response.created(URI.create("/persons/entity" + id)).build(); + }); + } + + @POST + @Path("/multiple") + public CompletionStage addPersons(List persons) { + return reactivePersonRepository.persist(persons); + } + + @PUT + public CompletionStage updatePerson(Person person) { + return reactivePersonRepository.update(person).thenApply(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public CompletionStage upsertPerson(Person person) { + return reactivePersonRepository.persistOrUpdate(person).thenApply(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public CompletionStage deletePerson(@PathParam("id") String id) { + return reactivePersonRepository.findById(Long.parseLong(id)) + .thenCompose(person -> reactivePersonRepository.delete(person)); + } + + @GET + @Path("/{id}") + public CompletionStage getPerson(@PathParam("id") String id) { + return reactivePersonRepository.findById(Long.parseLong(id)); + } + + @GET + @Path("/count") + public CompletionStage countAll() { + return reactivePersonRepository.count(); + } + + @DELETE + public CompletionStage deleteAll() { + return reactivePersonRepository.deleteAll().thenAccept(l -> { + }); + } +} diff --git a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/axle/NativeReactiveBookResourceIT.java b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/axle/NativeReactiveBookResourceIT.java new file mode 100644 index 0000000000000..9d25b6c7db2e6 --- /dev/null +++ b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/axle/NativeReactiveBookResourceIT.java @@ -0,0 +1,8 @@ +package io.quarkus.it.mongodb.panache.axle; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class NativeReactiveBookResourceIT extends ReactiveBookResourceTest { + +} diff --git a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/axle/ReactiveBookResourceTest.java b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/axle/ReactiveBookResourceTest.java new file mode 100644 index 0000000000000..bb50c7f1715b8 --- /dev/null +++ b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/axle/ReactiveBookResourceTest.java @@ -0,0 +1,366 @@ +package io.quarkus.it.mongodb.panache.axle; + +import static io.restassured.RestAssured.get; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.time.LocalDate; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.List; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.sse.SseEventSource; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import de.flapdoodle.embed.mongo.MongodExecutable; +import de.flapdoodle.embed.mongo.MongodStarter; +import de.flapdoodle.embed.mongo.config.IMongodConfig; +import de.flapdoodle.embed.mongo.config.MongodConfigBuilder; +import de.flapdoodle.embed.mongo.config.Net; +import de.flapdoodle.embed.mongo.distribution.Version; +import de.flapdoodle.embed.process.runtime.Network; +import io.quarkus.it.mongodb.panache.BookDTO; +import io.quarkus.it.mongodb.panache.book.BookDetail; +import io.quarkus.it.mongodb.panache.person.Person; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.common.mapper.TypeRef; +import io.restassured.config.ObjectMapperConfig; +import io.restassured.parsing.Parser; +import io.restassured.response.Response; + +@QuarkusTest +class ReactiveBookResourceTest { + private static final Logger LOGGER = LoggerFactory.getLogger(ReactiveBookResourceTest.class); + private static final TypeRef> LIST_OF_BOOK_TYPE_REF = new TypeRef>() { + }; + private static final TypeRef> LIST_OF_PERSON_TYPE_REF = new TypeRef>() { + }; + + private static MongodExecutable MONGO; + + @BeforeAll + public static void startMongoDatabase() throws IOException { + Version.Main version = Version.Main.V4_0; + int port = 27018; + LOGGER.info("Starting Mongo {} on port {}", version, port); + IMongodConfig config = new MongodConfigBuilder() + .version(version) + .net(new Net(port, Network.localhostIsIPv6())) + .build(); + MONGO = MongodStarter.getDefaultInstance().prepare(config); + MONGO.start(); + } + + @AfterAll + public static void stopMongoDatabase() { + if (MONGO != null) { + MONGO.stop(); + } + } + + @Test + public void testReactiveBookEntity() throws InterruptedException { + callReactiveBookEndpoint("/axle/books/entity"); + } + + @Test + public void testReactiveBookRepository() throws InterruptedException { + callReactiveBookEndpoint("/axle/books/repository"); + } + + @Test + public void testReactivePersonEntity() { + callReactivePersonEndpoint("/axle/persons/entity"); + } + + @Test + public void testReactivePersonRepository() { + callReactivePersonEndpoint("/axle/persons/repository"); + } + + private void callReactiveBookEndpoint(String endpoint) throws InterruptedException { + RestAssured.defaultParser = Parser.JSON; + ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + RestAssured.config + .objectMapperConfig(new ObjectMapperConfig().jackson2ObjectMapperFactory((type, s) -> objectMapper)); + + List list = get(endpoint).as(LIST_OF_BOOK_TYPE_REF); + assertEquals(0, list.size()); + + BookDTO book1 = new BookDTO().setAuthor("Victor Hugo").setTitle("Les Misérables") + .setCreationDate(yearToDate(1886)) + .setCategories(Arrays.asList("long", "very long")) + .setDetails(new BookDetail().setRating(3).setSummary("A very long book")); + Response response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book1) + .post(endpoint) + .andReturn(); + assertEquals(201, response.statusCode()); + Assertions.assertTrue(response.header("Location").length() > 20);//Assert that id has been populated + + BookDTO book2 = new BookDTO().setAuthor("Victor Hugo").setTitle("Notre-Dame de Paris") + .setCreationDate(yearToDate(1831)) + .setCategories(Arrays.asList("long", "quasimodo")) + .setDetails(new BookDetail().setRating(4).setSummary("quasimodo and esmeralda")); + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book2) + .post(endpoint) + .andReturn(); + assertEquals(201, response.statusCode()); + + list = get(endpoint).as(LIST_OF_BOOK_TYPE_REF); + assertEquals(2, list.size()); + + BookDTO book3 = new BookDTO().setAuthor("Charles Baudelaire").setTitle("Les fleurs du mal") + .setCreationDate(yearToDate(1857)) + .setCategories(Collections.singletonList("poem")) + .setDetails(new BookDetail().setRating(2).setSummary("Les Fleurs du mal is a volume of poetry.")); + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book3) + .post(endpoint) + .andReturn(); + assertEquals(201, response.statusCode()); + + BookDTO book4 = new BookDTO().setAuthor("Charles Baudelaire").setTitle("Le Spleen de Paris") + .setCreationDate(yearToDate(1869)) + .setCategories(Collections.singletonList("poem")) + .setDetails(new BookDetail().setRating(2) + .setSummary("Le Spleen de Paris is a collection of 50 short prose poems.")); + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book4) + .patch(endpoint) + .andReturn(); + assertEquals(202, response.statusCode()); + + list = get(endpoint).as(LIST_OF_BOOK_TYPE_REF); + assertEquals(4, list.size()); + + //with sort + list = get(endpoint + "?sort=author").as(LIST_OF_BOOK_TYPE_REF); + assertEquals(4, list.size()); + + // magic query find("author", author) + list = get(endpoint + "/search/Victor Hugo").as(LIST_OF_BOOK_TYPE_REF); + assertEquals(2, list.size()); + + // magic query find("{'author':?1,'title':?1}", author, title) + BookDTO book = get(endpoint + "/search?author=Victor Hugo&title=Notre-Dame de Paris").as(BookDTO.class); + assertNotNull(book); + + // date + book = get(endpoint + "/search?dateFrom=1885-01-01&dateTo=1887-01-01").as(BookDTO.class); + assertNotNull(book); + + book = get(endpoint + "/search2?dateFrom=1885-01-01&dateTo=1887-01-01").as(BookDTO.class); + assertNotNull(book); + + // magic query find("{'author'::author,'title'::title}", Parameters.with("author", author).and("title", title)) + book = get(endpoint + "/search2?author=Victor Hugo&title=Notre-Dame de Paris").as(BookDTO.class); + assertNotNull(book); + assertNotNull(book.getId()); + assertNotNull(book.getDetails()); + + //update a book + book.setTitle("Notre-Dame de Paris 2").setTransientDescription("should not be persisted"); + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book) + .put(endpoint) + .andReturn(); + assertEquals(202, response.statusCode()); + + //check that the title has been updated and the transient description ignored + book = get(endpoint + "/" + book.getId().toString()).as(BookDTO.class); + assertEquals("Notre-Dame de Paris 2", book.getTitle()); + Assertions.assertNull(book.getTransientDescription()); + + //delete a book + response = RestAssured + .given() + .delete(endpoint + "/" + book.getId().toString()) + .andReturn(); + assertEquals(204, response.statusCode()); + + list = get(endpoint).as(LIST_OF_BOOK_TYPE_REF); + assertEquals(3, list.size()); + + //test some special characters + list = get(endpoint + "/search/Victor'\\ Hugo").as(LIST_OF_BOOK_TYPE_REF); + assertEquals(0, list.size()); + + //test SSE : there is no JSON serialization for SSE so the test is not very elegant ... + Client client = ClientBuilder.newClient(); + WebTarget target = client.target("http://localhost:8081" + endpoint + "/stream"); + try (SseEventSource source = SseEventSource.target(target).build()) { + final IntegerAdder nbEvent = new IntegerAdder(); + source.register((inboundSseEvent) -> { + try { + BookDTO theBook = objectMapper.readValue(inboundSseEvent.readData(), BookDTO.class); + assertNotNull(theBook); + } catch (IOException e) { + throw new RuntimeException(e); + } + nbEvent.increment(); + }); + source.open(); + Thread.sleep(100);//wait a little for the events to comes in + assertEquals(3, nbEvent.count()); + } + } + + private void callReactivePersonEndpoint(String endpoint) { + RestAssured.defaultParser = Parser.JSON; + RestAssured.config + .objectMapperConfig(new ObjectMapperConfig().jackson2ObjectMapperFactory((type, s) -> new ObjectMapper() + .registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS))); + + List list = get(endpoint).as(LIST_OF_PERSON_TYPE_REF); + assertEquals(0, list.size()); + + Person person1 = new Person(); + person1.id = 1L; + person1.firstname = "John"; + person1.lastname = "Doe"; + Response response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(person1) + .post(endpoint) + .andReturn(); + assertEquals(201, response.statusCode()); + + Person person2 = new Person(); + person2.id = 2L; + person2.firstname = "Jane"; + person2.lastname = "Doe"; + Person person3 = new Person(); + person3.id = 3L; + person3.firstname = "Victor"; + person3.lastname = "Hugo"; + List persons = new ArrayList<>(); + persons.add(person2); + persons.add(person3); + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(persons) + .post(endpoint + "/multiple") + .andReturn(); + assertEquals(204, response.statusCode()); + + Person person4 = new Person(); + person4.id = 4L; + person4.firstname = "Charles"; + person4.lastname = "Baudelaire"; + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(person4) + .patch(endpoint) + .andReturn(); + assertEquals(202, response.statusCode()); + + list = get(endpoint).as(LIST_OF_PERSON_TYPE_REF); + assertEquals(4, list.size()); + + //with sort + list = get(endpoint + "?sort=firstname").as(LIST_OF_PERSON_TYPE_REF); + assertEquals(4, list.size()); + + //count + Long count = get(endpoint + "/count").as(Long.class); + assertEquals(4, count); + + //update a person + person3.lastname = "Webster"; + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(person3) + .put(endpoint) + .andReturn(); + assertEquals(202, response.statusCode()); + + //check that the title has been updated + person3 = get(endpoint + "/" + person3.id.toString()).as(Person.class); + assertEquals(3L, person3.id); + assertEquals("Webster", person3.lastname); + + //delete a person + response = RestAssured + .given() + .delete(endpoint + "/" + person3.id.toString()) + .andReturn(); + assertEquals(204, response.statusCode()); + + count = get(endpoint + "/count").as(Long.class); + assertEquals(3, count); + + //delete all + response = RestAssured + .given() + .delete(endpoint) + .andReturn(); + assertEquals(204, response.statusCode()); + + count = get(endpoint + "/count").as(Long.class); + assertEquals(0, count); + } + + private Date yearToDate(int year) { + Calendar cal = new GregorianCalendar(); + cal.set(year, 1, 1); + return cal.getTime(); + } + + private Date fromYear(int year) { + return Date.from(LocalDate.of(year, 1, 1).atStartOfDay().toInstant(ZoneOffset.UTC)); + } + + private class IntegerAdder { + int cpt = 0; + + public void increment() { + cpt++; + } + + public int count() { + return cpt; + } + } +}