From da12ed00b596e88fcf90b1ffaf706cd2d4d59dec Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Fri, 21 Feb 2020 17:12:48 +0100 Subject: [PATCH 1/5] Rename the Axle enhancers for entities and repositories. So it does not conflict when the Mutiny enhancers are introduced. --- ...ityEnhancer.java => AxlePanacheMongoEntityEnhancer.java} | 4 ++-- ...nhancer.java => AxlePanacheMongoRepositoryEnhancer.java} | 6 +++--- .../panache/deployment/PanacheResourceProcessor.java | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) rename extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/{ReactivePanacheMongoEntityEnhancer.java => AxlePanacheMongoEntityEnhancer.java} (94%) rename extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/{ReactivePanacheMongoRepositoryEnhancer.java => AxlePanacheMongoRepositoryEnhancer.java} (89%) 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/AxlePanacheMongoEntityEnhancer.java similarity index 94% rename from extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java rename to extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/AxlePanacheMongoEntityEnhancer.java index e964c5bcd59b1..9e2ee28c34238 100644 --- 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/AxlePanacheMongoEntityEnhancer.java @@ -20,7 +20,7 @@ import io.quarkus.panache.common.deployment.MetamodelInfo; import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; -public class ReactivePanacheMongoEntityEnhancer extends PanacheEntityEnhancer>> { +public class AxlePanacheMongoEntityEnhancer extends PanacheEntityEnhancer>> { public final static String MONGO_OPERATIONS_NAME = ReactiveMongoOperations.class.getName(); public final static String MONGO_OPERATIONS_BINARY_NAME = MONGO_OPERATIONS_NAME.replace('.', '/'); @@ -28,7 +28,7 @@ public class ReactivePanacheMongoEntityEnhancer extends PanacheEntityEnhancer entities = new HashMap<>(); - public ReactivePanacheMongoEntityEnhancer(IndexView index) { + public AxlePanacheMongoEntityEnhancer(IndexView index) { super(index, PanacheResourceProcessor.DOTNAME_AXLE_PANACHE_ENTITY_BASE); modelInfo = new MetamodelInfo<>(); } 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/AxlePanacheMongoRepositoryEnhancer.java similarity index 89% rename from extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoRepositoryEnhancer.java rename to extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/AxlePanacheMongoRepositoryEnhancer.java index b26938f15d55c..3d06525caed83 100644 --- 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/AxlePanacheMongoRepositoryEnhancer.java @@ -10,13 +10,13 @@ import io.quarkus.mongodb.panache.axle.ReactivePanacheMongoRepositoryBase; import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; -public class ReactivePanacheMongoRepositoryEnhancer extends PanacheRepositoryEnhancer { +public class AxlePanacheMongoRepositoryEnhancer 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) { + public AxlePanacheMongoRepositoryEnhancer(IndexView index) { super(index, PanacheResourceProcessor.DOTNAME_AXLE_PANACHE_REPOSITORY_BASE); } @@ -45,7 +45,7 @@ protected DotName getPanacheRepositoryBaseDotName() { @Override protected String getPanacheOperationsBinaryName() { - return ReactivePanacheMongoEntityEnhancer.MONGO_OPERATIONS_BINARY_NAME; + return AxlePanacheMongoEntityEnhancer.MONGO_OPERATIONS_BINARY_NAME; } @Override 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 43b8145b10d4e..3de1f025f7a07 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 @@ -304,7 +304,7 @@ void buildAxle(CombinedIndexBuildItem index, ApplicationIndexBuildItem applicationIndex, BuildProducer transformers) throws Exception { - ReactivePanacheMongoRepositoryEnhancer daoEnhancer = new ReactivePanacheMongoRepositoryEnhancer(index.getIndex()); + AxlePanacheMongoRepositoryEnhancer daoEnhancer = new AxlePanacheMongoRepositoryEnhancer(index.getIndex()); Set daoClasses = new HashSet<>(); for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_AXLE_PANACHE_REPOSITORY_BASE)) { // Skip PanacheRepository @@ -323,7 +323,7 @@ void buildAxle(CombinedIndexBuildItem index, transformers.produce(new BytecodeTransformerBuildItem(daoClass, daoEnhancer)); } - ReactivePanacheMongoEntityEnhancer modelEnhancer = new ReactivePanacheMongoEntityEnhancer(index.getIndex()); + AxlePanacheMongoEntityEnhancer modelEnhancer = new AxlePanacheMongoEntityEnhancer(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 From 4fc39ce49ba8ceea2a21fa98b66d0749eb69b84d Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Fri, 21 Feb 2020 17:16:46 +0100 Subject: [PATCH 2/5] Define and implement Mutiny-based MongoDB entity and repository. --- .../reactive/ReactivePanacheMongoEntity.java | 30 + .../ReactivePanacheMongoEntityBase.java | 892 ++++++++++++++++++ .../ReactivePanacheMongoRepository.java | 15 + .../ReactivePanacheMongoRepositoryBase.java | 889 +++++++++++++++++ .../reactive/ReactivePanacheQuery.java | 178 ++++ .../runtime/ReactiveMongoOperations.java | 588 ++++++++++++ .../runtime/ReactivePanacheQueryImpl.java | 176 ++++ .../runtime/ReactiveMongoOperationsTest.java | 245 +++++ 8 files changed, 3013 insertions(+) create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoEntity.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoEntityBase.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoRepository.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoRepositoryBase.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheQuery.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperations.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactivePanacheQueryImpl.java create mode 100644 extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperationsTest.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoEntity.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoEntity.java new file mode 100644 index 0000000000000..44532818103f4 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoEntity.java @@ -0,0 +1,30 @@ +package io.quarkus.mongodb.panache.reactive; + +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/reactive/ReactivePanacheMongoEntityBase.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoEntityBase.java new file mode 100644 index 0000000000000..9bc3f8e811b86 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoEntityBase.java @@ -0,0 +1,892 @@ +package io.quarkus.mongodb.panache.reactive; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import org.bson.Document; + +import io.quarkus.mongodb.panache.reactive.runtime.ReactiveMongoOperations; +import io.quarkus.mongodb.reactive.ReactiveMongoCollection; +import io.quarkus.mongodb.reactive.ReactiveMongoDatabase; +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; +import io.quarkus.panache.common.impl.GenerateBridge; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +/** + * 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 Uni persist() { + return ReactiveMongoOperations.persist(this); + } + + /** + * Update this entity in the database. + * + * @see #update(Iterable) + * @see #update(Stream) + * @see #update(Object, Object...) + */ + public Uni 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 Uni 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 Uni 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 Uni 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 Uni> 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 Uni> 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 Uni> 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 Uni> 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 Uni> 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 Uni> 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 Uni> 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 Uni> 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 Uni> 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 Uni> 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 Uni> 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 Multi 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 Multi 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 Multi 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 Multi 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 Multi 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 Multi 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 Multi 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 Multi 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 Multi 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 Multi 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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 Uni 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/reactive/ReactivePanacheMongoRepository.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoRepository.java new file mode 100644 index 0000000000000..07af6e4895bc8 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoRepository.java @@ -0,0 +1,15 @@ +package io.quarkus.mongodb.panache.reactive; + +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/reactive/ReactivePanacheMongoRepositoryBase.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoRepositoryBase.java new file mode 100644 index 0000000000000..23001024b08a2 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheMongoRepositoryBase.java @@ -0,0 +1,889 @@ +package io.quarkus.mongodb.panache.reactive; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import org.bson.Document; + +import io.quarkus.mongodb.panache.reactive.runtime.ReactiveMongoOperations; +import io.quarkus.mongodb.reactive.ReactiveMongoCollection; +import io.quarkus.mongodb.reactive.ReactiveMongoDatabase; +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; +import io.quarkus.panache.common.impl.GenerateBridge; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +/** + * 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...) + */ + default Uni 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...) + */ + default Uni 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...) + */ + default Uni 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() + */ + default Uni 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 + default Uni 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 + default Uni> 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + default Uni> 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 + default Uni> 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 + default Uni> 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 + default Uni> 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 + default Uni> 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 + default Uni> 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 + 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 + 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 + default Uni> 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 + default Uni> 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 + default Multi 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 + default Multi 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 + default Multi 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 + default Multi 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 + default Multi 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 + default Multi 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 + default Multi 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 + default Multi 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 + default Multi 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 + default Multi 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 + default Uni 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 + default Uni 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 + default Uni 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 + default Uni 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 + default Uni 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 + default Uni 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 + default Uni 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 + default Uni 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 + default Uni 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 + default Uni 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...) + */ + default Uni 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...) + */ + default Uni 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) + */ + default Uni 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...) + */ + default Uni 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...) + */ + default Uni 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) + */ + default Uni 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...) + */ + default Uni 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...) + */ + default Uni 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) + */ + default Uni persistOrUpdate(Entity firstEntity, + @SuppressWarnings("unchecked") Entity... entities) { + return ReactiveMongoOperations.persistOrUpdate(firstEntity, entities); + } + + /** + * Allow to access the underlying Mongo Collection + */ + @GenerateBridge + default ReactiveMongoCollection mongoCollection() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } + + /** + * Allow to access the underlying Mongo Database. + */ + @GenerateBridge + default ReactiveMongoDatabase mongoDatabase() { + throw ReactiveMongoOperations.implementationInjectionMissing(); + } +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheQuery.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheQuery.java new file mode 100644 index 0000000000000..7916c53ff02ec --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheQuery.java @@ -0,0 +1,178 @@ +package io.quarkus.mongodb.panache.reactive; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import io.quarkus.panache.common.Page; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +/** + * 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() + */ + 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() + */ + ReactivePanacheQuery page(int pageIndex, int pageSize); + + /** + * Sets the current page to the next page + * + * @return this query, modified + * @see #previousPage() + */ + 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() + */ + ReactivePanacheQuery previousPage(); + + /** + * Sets the current page to the first page + * + * @return this query, modified + * @see #lastPage() + */ + 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() + */ + Uni> 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() + */ + Uni 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() + */ + 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. + */ + Uni pageCount(); + + /** + * Returns the current page. + * + * @return the current page + * @see #page(Page) + * @see #page(int,int) + */ + 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. + */ + Uni 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() + */ + Uni> 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() + */ + Multi asMulti(); + + /** + * 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() + */ + Uni 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() + */ + Uni> 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() + */ + Uni 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() + */ + Uni> singleResultOptional(); +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperations.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperations.java new file mode 100644 index 0000000000000..a31d22bf389d1 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperations.java @@ -0,0 +1,588 @@ +package io.quarkus.mongodb.panache.reactive.runtime; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +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.jboss.logging.Logger; + +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 com.mongodb.client.result.DeleteResult; + +import io.quarkus.arc.Arc; +import io.quarkus.mongodb.panache.MongoEntity; +import io.quarkus.mongodb.panache.binder.NativeQueryBinder; +import io.quarkus.mongodb.panache.binder.PanacheQlQueryBinder; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery; +import io.quarkus.mongodb.reactive.ReactiveMongoClient; +import io.quarkus.mongodb.reactive.ReactiveMongoCollection; +import io.quarkus.mongodb.reactive.ReactiveMongoDatabase; +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +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 Uni persist(Object entity) { + ReactiveMongoCollection collection = mongoCollection(entity); + return persist(collection, entity); + } + + public static Uni persist(Iterable entities) { + return Uni.createFrom().deferred(() -> { + // 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 Uni.createFrom().item((Void) null); + }); + } + + public static Uni 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 Uni persist(Stream entities) { + return Uni.createFrom().deferred(() -> { + 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 Uni.createFrom().item((Void) null); + }); + } + + public static Uni update(Object entity) { + ReactiveMongoCollection collection = mongoCollection(entity); + return update(collection, entity); + } + + public static Uni update(Iterable entities) { + return Uni.createFrom().deferred(() -> { + // 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 Uni.createFrom().item((Void) null); + }); + } + + public static Uni 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 Uni update(Stream entities) { + return Uni.createFrom().deferred(() -> { + 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 Uni.createFrom().item((Void) null); + }); + } + + public static Uni persistOrUpdate(Object entity) { + ReactiveMongoCollection collection = mongoCollection(entity); + return persistOrUpdate(collection, entity); + } + + public static Uni 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 Uni.createFrom().item((Void) null); + } + + public static Uni 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 Uni persistOrUpdate(Stream entities) { + return Uni.createFrom().deferred(() -> { + 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 Uni.createFrom().item((Void) null); + }); + } + + public static Uni 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) + .onItem().ignore().andContinueWithNull(); + } + + 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 + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Uni persist(ReactiveMongoCollection collection, Object entity) { + return collection.insertOne(entity); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Uni persist(ReactiveMongoCollection collection, List entities) { + return collection.insertMany(entities); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Uni 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) + .onItem().ignore().andContinueWithNull(); + } + + private static Uni update(ReactiveMongoCollection collection, List entities) { + Uni uni = Uni.createFrom().item((Void) null); + for (Object entity : entities) { + uni = uni.and(update(collection, entity)); + } + return uni.onItem().ignore().andContinueWithNull(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Uni 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))) + .onItem().ignore().andContinueWithNull(); + } + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static Uni 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) + .onItem().ignore().andContinueWithNull(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + 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 Uni findById(Class entityClass, Object id) { + Uni> optionalEntity = findByIdOptional(entityClass, id); + return optionalEntity + .onItem().apply(optional -> optional.orElse(null)); + } + + public static Uni> findByIdOptional(Class entityClass, Object id) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.find(new Document(ID, id)) + .collectItems().first() + .onItem().apply(Optional::ofNullable); + } + + 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); + } + + 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()); + } + + 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 Uni> list(Class entityClass, String query, Object... params) { + return (Uni) find(entityClass, query, params).list(); + } + + public static Uni> list(Class entityClass, String query, Sort sort, Object... params) { + return (Uni) find(entityClass, query, sort, params).list(); + } + + public static Uni> list(Class entityClass, String query, Map params) { + return (Uni) find(entityClass, query, params).list(); + } + + public static Uni> list(Class entityClass, String query, Sort sort, Map params) { + return (Uni) find(entityClass, query, sort, params).list(); + } + + public static Uni> list(Class entityClass, String query, Parameters params) { + return (Uni) find(entityClass, query, params).list(); + } + + public static Uni> list(Class entityClass, String query, Sort sort, Parameters params) { + return (Uni) find(entityClass, query, sort, params).list(); + } + + //specific Mongo query + public static Uni> list(Class entityClass, Document query) { + return (Uni) find(entityClass, query).list(); + } + + //specific Mongo query + public static Uni> list(Class entityClass, Document query, Document sort) { + return (Uni) find(entityClass, query, sort).list(); + } + + public static Multi stream(Class entityClass, String query, Object... params) { + return find(entityClass, query, params).asMulti(); + } + + public static Multi stream(Class entityClass, String query, Sort sort, Object... params) { + return find(entityClass, query, sort, params).asMulti(); + } + + public static Multi stream(Class entityClass, String query, Map params) { + return find(entityClass, query, params).asMulti(); + } + + public static Multi stream(Class entityClass, String query, Sort sort, Map params) { + return find(entityClass, query, sort, params).asMulti(); + } + + public static Multi stream(Class entityClass, String query, Parameters params) { + return find(entityClass, query, params).asMulti(); + } + + public static Multi stream(Class entityClass, String query, Sort sort, Parameters params) { + return find(entityClass, query, sort, params).asMulti(); + } + + //specific Mongo query + public static Multi stream(Class entityClass, Document query) { + return find(entityClass, query).asMulti(); + } + + //specific Mongo query + public static Multi stream(Class entityClass, Document query, Document sort) { + return find(entityClass, query, sort).asMulti(); + } + + public static ReactivePanacheQuery findAll(Class entityClass) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return new ReactivePanacheQueryImpl<>(collection, entityClass, null, null); + } + + 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 Uni> listAll(Class entityClass) { + return (Uni) findAll(entityClass).list(); + } + + public static Uni> listAll(Class entityClass, Sort sort) { + return (Uni) findAll(entityClass, sort).list(); + } + + public static Multi streamAll(Class entityClass) { + return findAll(entityClass).asMulti(); + } + + public static Multi streamAll(Class entityClass, Sort sort) { + return findAll(entityClass, sort).asMulti(); + } + + public static Uni count(Class entityClass) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.countDocuments(); + } + + public static Uni 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 Uni 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 Uni count(Class entityClass, String query, Parameters params) { + return count(entityClass, query, params.map()); + } + + //specific Mongo query + public static Uni count(Class entityClass, Document query) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.countDocuments(query); + } + + public static Uni deleteAll(Class entityClass) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.deleteMany(new Document()).map(DeleteResult::getDeletedCount); + } + + public static Uni 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).map(DeleteResult::getDeletedCount); + } + + public static Uni 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).map(DeleteResult::getDeletedCount); + } + + public static Uni delete(Class entityClass, String query, Parameters params) { + return delete(entityClass, query, params.map()); + } + + //specific Mongo query + public static Uni delete(Class entityClass, Document query) { + ReactiveMongoCollection collection = mongoCollection(entityClass); + return collection.deleteMany(query).map(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/reactive/runtime/ReactivePanacheQueryImpl.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactivePanacheQueryImpl.java new file mode 100644 index 0000000000000..aeb50743d5971 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactivePanacheQueryImpl.java @@ -0,0 +1,176 @@ +package io.quarkus.mongodb.panache.reactive.runtime; + +import java.util.List; +import java.util.Optional; + +import org.bson.Document; + +import io.quarkus.mongodb.FindOptions; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery; +import io.quarkus.mongodb.reactive.ReactiveMongoCollection; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.exception.PanacheQueryException; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +public class ReactivePanacheQueryImpl implements ReactivePanacheQuery { + private ReactiveMongoCollection collection; + 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 Uni count; + + ReactivePanacheQueryImpl(ReactiveMongoCollection collection, Class entityClass, + Document mongoQuery, + Document sort) { + this.collection = collection; + 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 Uni> lastPage() { + return pageCount().map(pageCount -> page(page.index(pageCount - 1))); + } + + @Override + public Uni hasNextPage() { + return pageCount().map(pageCount -> page.index < (pageCount - 1)); + } + + @Override + public boolean hasPreviousPage() { + return page.index > 0; + } + + @Override + public Uni pageCount() { + return count().map(c -> { + if (c == 0) { + return 1; // a single page of zero results + } + return (int) Math.ceil((double) c / (double) page.size); + }); + } + + @Override + public Page page() { + return page; + } + + // Results + + @Override + public Uni count() { + if (count == null) { + count = collection.countDocuments(mongoQuery); + } + return count; + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Uni> list() { + FindOptions options = new FindOptions(); + options.sort(sort).skip(page.index).limit(page.size); + Multi results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + return results.collectItems().asList(); + } + + @Override + @SuppressWarnings("unchecked") + public Multi asMulti() { + FindOptions options = new FindOptions(); + options.sort(sort).skip(page.index).limit(page.size); + return mongoQuery == null ? (Multi) collection.find(options) : (Multi) collection.find(mongoQuery, options); + } + + @Override + public Uni firstResult() { + Uni> optionalEntity = firstResultOptional(); + return optionalEntity.map(optional -> optional.orElse(null)); + } + + @SuppressWarnings("unchecked") + @Override + public Uni> firstResultOptional() { + FindOptions options = new FindOptions(); + options.sort(sort).skip(page.index).limit(1); + Multi multi = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + return multi + .collectItems().first() + .map(x -> (T) x) + .map(Optional::ofNullable); + } + + @Override + @SuppressWarnings("unchecked") + public Uni singleResult() { + FindOptions options = new FindOptions(); + options.sort(sort).skip(page.index).limit(2); + Multi results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + return results + .collectItems().asList() + .onItem().produceUni(list -> { + if (list.size() != 1) { + return Uni.createFrom().failure(new PanacheQueryException("There should be only one result")); + } else { + return Uni.createFrom().item((T) list.get(0)); + } + }); + } + + @SuppressWarnings("unchecked") + @Override + public Uni> singleResultOptional() { + FindOptions options = new FindOptions(); + options.sort(sort).skip(page.index).limit(2); + Multi results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + return results + .collectItems().asList() + .onItem().produceUni(list -> { + if (list.size() > 1) { + return Uni.createFrom().failure(new PanacheQueryException("There should be only one result")); + } else if (list.isEmpty()) { + return Uni.createFrom().item(Optional.empty()); + } else { + return Uni.createFrom().item(Optional.of((T) list.get(0))); + } + }); + } +} diff --git a/extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperationsTest.java b/extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperationsTest.java new file mode 100644 index 0000000000000..127c0e36603b1 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperationsTest.java @@ -0,0 +1,245 @@ +package io.quarkus.mongodb.panache.reactive.runtime; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.bson.codecs.pojo.annotations.BsonProperty; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import io.quarkus.mongodb.panache.runtime.MongoPropertyUtil; +import io.quarkus.panache.common.Parameters; + +public class ReactiveMongoOperationsTest { + + private static class DemoObj { + public String field; + public boolean isOk; + @BsonProperty("value") + public String property; + } + + @BeforeAll + static void setupFieldReplacement() { + Map> replacementCache = new HashMap<>(); + Map replacementMap = new HashMap<>(); + replacementMap.put("property", "value"); + replacementCache.put(DemoObj.class.getName(), replacementMap); + replacementCache.put(Object.class.getName(), Collections.emptyMap());//because the test use Object ... + MongoPropertyUtil.setReplacementCache(replacementCache); + } + + @Test + public void testBindShorthandQuery() { + String query = ReactiveMongoOperations.bindQuery(Object.class, "field", new Object[] { "a value" }); + assertEquals("{'field':'a value'}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field", new Object[] { true }); + assertEquals("{'field':true}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field", new Object[] { LocalDate.of(2019, 3, 4) }); + assertEquals("{'field':ISODate('2019-03-04')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field", + new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1) }); + assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field", + new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1).toInstant(ZoneOffset.UTC) }); + assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field", + new Object[] { toDate(LocalDateTime.of(2019, 3, 4, 1, 1, 1)) }); + assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query); + + //test field replacement + query = ReactiveMongoOperations.bindQuery(DemoObj.class, "property", new Object[] { "a value" }); + assertEquals("{'value':'a value'}", query); + } + + private Object toDate(LocalDateTime of) { + return Date.from(of.atZone(ZoneId.of("UTC")).toInstant()); + } + + @Test + public void testBindNativeQueryByIndex() { + String query = ReactiveMongoOperations.bindQuery(DemoObj.class, "{'field': ?1}", new Object[] { "a value" }); + assertEquals("{'field': 'a value'}", query); + + query = ReactiveMongoOperations.bindQuery(DemoObj.class, "{'field.sub': ?1}", new Object[] { "a value" }); + assertEquals("{'field.sub': 'a value'}", query); + + //test that there are no field replacement for native queries + query = ReactiveMongoOperations.bindQuery(DemoObj.class, "{'property': ?1}", new Object[] { "a value" }); + assertEquals("{'property': 'a value'}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field': ?1}", + new Object[] { LocalDate.of(2019, 3, 4) }); + assertEquals("{'field': ISODate('2019-03-04')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field': ?1}", + new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1) }); + assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field': ?1}", + new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1).toInstant(ZoneOffset.UTC) }); + assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field': ?1}", + new Object[] { toDate(LocalDateTime.of(2019, 3, 4, 1, 1, 1)) }); + assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field': ?1, 'isOk': ?2}", new Object[] { "a value", true }); + assertEquals("{'field': 'a value', 'isOk': true}", query); + } + + @Test + public void testBindNativeQueryByName() { + String query = ReactiveMongoOperations.bindQuery(Object.class, "{'field': :field}", + Parameters.with("field", "a value").map()); + assertEquals("{'field': 'a value'}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field.sub': :field}", + Parameters.with("field", "a value").map()); + assertEquals("{'field.sub': 'a value'}", query); + + //test that there are no field replacement for native queries + query = ReactiveMongoOperations.bindQuery(DemoObj.class, "{'property': :field}", + Parameters.with("field", "a value").map()); + assertEquals("{'property': 'a value'}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field': :field}", + Parameters.with("field", LocalDate.of(2019, 3, 4)).map()); + assertEquals("{'field': ISODate('2019-03-04')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field': :field}", + Parameters.with("field", LocalDateTime.of(2019, 3, 4, 1, 1, 1)).map()); + assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field': :field}", + Parameters.with("field", LocalDateTime.of(2019, 3, 4, 1, 1, 1).toInstant(ZoneOffset.UTC)).map()); + assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field': :field}", + Parameters.with("field", toDate(LocalDateTime.of(2019, 3, 4, 1, 1, 1))).map()); + assertEquals("{'field': ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field': :field, 'isOk': :isOk}", + Parameters.with("field", "a value").and("isOk", true).map()); + assertEquals("{'field': 'a value', 'isOk': true}", query); + } + + @Test + public void testBindEnhancedQueryByIndex() { + String query = ReactiveMongoOperations.bindQuery(Object.class, "field = ?1", new Object[] { "a value" }); + assertEquals("{'field':'a value'}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "{'field.sub': :field}", + Parameters.with("field", "a value").map()); + assertEquals("{'field.sub': 'a value'}", query); + + //test field replacement + query = ReactiveMongoOperations.bindQuery(DemoObj.class, "property = ?1", new Object[] { "a value" }); + assertEquals("{'value':'a value'}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = ?1", new Object[] { LocalDate.of(2019, 3, 4) }); + assertEquals("{'field':ISODate('2019-03-04')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = ?1", + new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1) }); + assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = ?1", + new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1).toInstant(ZoneOffset.UTC) }); + assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = ?1", + new Object[] { toDate(LocalDateTime.of(2019, 3, 4, 1, 1, 1)) }); + assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = ?1 and isOk = ?2", new Object[] { "a value", true }); + assertEquals("{'field':'a value','isOk':true}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = ?1 or isOk = ?2", new Object[] { "a value", true }); + assertEquals("{'$or':[{'field':'a value'},{'isOk':true}]}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "count >= ?1 and count < ?2", new Object[] { 5, 10 }); + assertEquals("{'count':{'$gte':5},'count':{'$lt':10}}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field != ?1", new Object[] { "a value" }); + assertEquals("{'field':{'$ne':'a value'}}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field like ?1", new Object[] { "a value" }); + assertEquals("{'field':{'$regex':'a value'}}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field is not null", new Object[] {}); + assertEquals("{'field':{'$exists':true}}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field is null", new Object[] {}); + assertEquals("{'field':{'$exists':false}}", query); + + // test with hardcoded value + query = ReactiveMongoOperations.bindQuery(Object.class, "field = 'some hardcoded value'", new Object[] {}); + assertEquals("{'field':'some hardcoded value'}", query); + } + + @Test + public void testBindEnhancedQueryByName() { + String query = ReactiveMongoOperations.bindQuery(Object.class, "field = :field", + Parameters.with("field", "a value").map()); + assertEquals("{'field':'a value'}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field.sub = :field", + Parameters.with("field", "a value").map()); + assertEquals("{'field.sub':'a value'}", query); + + //test field replacement + query = ReactiveMongoOperations.bindQuery(DemoObj.class, "property = :field", + Parameters.with("field", "a value").map()); + assertEquals("{'value':'a value'}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = :field", + Parameters.with("field", LocalDate.of(2019, 3, 4)).map()); + assertEquals("{'field':ISODate('2019-03-04')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = :field", + Parameters.with("field", LocalDateTime.of(2019, 3, 4, 1, 1, 1)).map()); + assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = :field", + Parameters.with("field", LocalDateTime.of(2019, 3, 4, 1, 1, 1).toInstant(ZoneOffset.UTC)).map()); + assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = :field", + Parameters.with("field", toDate(LocalDateTime.of(2019, 3, 4, 1, 1, 1))).map()); + assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = :field and isOk = :isOk", + Parameters.with("field", "a value").and("isOk", true).map()); + assertEquals("{'field':'a value','isOk':true}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field = :field or isOk = :isOk", + Parameters.with("field", "a value").and("isOk", true).map()); + assertEquals("{'$or':[{'field':'a value'},{'isOk':true}]}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "count > :lower and count <= :upper", + Parameters.with("lower", 5).and("upper", 10).map()); + assertEquals("{'count':{'$gt':5},'count':{'$lte':10}}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field != :field", + Parameters.with("field", "a value").map()); + assertEquals("{'field':{'$ne':'a value'}}", query); + + query = ReactiveMongoOperations.bindQuery(Object.class, "field like :field", + Parameters.with("field", "a value").map()); + assertEquals("{'field':{'$regex':'a value'}}", query); + } +} From 7e1f84764e68de86c136e05f91434eddbb1fb8cb Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Fri, 21 Feb 2020 17:18:01 +0100 Subject: [PATCH 3/5] Add Mutiny based enhancers to the processor --- .../MutinyPanacheMongoEntityEnhancer.java | 86 +++++++++++++++++++ .../MutinyPanacheMongoRepositoryEnhancer.java | 62 +++++++++++++ .../deployment/PanacheResourceProcessor.java | 67 +++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/MutinyPanacheMongoEntityEnhancer.java create mode 100644 extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/MutinyPanacheMongoRepositoryEnhancer.java diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/MutinyPanacheMongoEntityEnhancer.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/MutinyPanacheMongoEntityEnhancer.java new file mode 100644 index 0000000000000..c6dd3cf8e3d64 --- /dev/null +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/MutinyPanacheMongoEntityEnhancer.java @@ -0,0 +1,86 @@ +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.reactive.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 MutinyPanacheMongoEntityEnhancer 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 MutinyPanacheMongoEntityEnhancer(IndexView index) { + super(index, PanacheResourceProcessor.DOTNAME_MUTINY_PANACHE_ENTITY_BASE); + modelInfo = new MetamodelInfo<>(); + } + + @Override + public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { + return new PanacheMongoEntityClassVisitor(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, + indexView.getClassByName(DotName.createSimple(className))); + } + + static class PanacheMongoEntityClassVisitor extends PanacheEntityClassVisitor { + + public PanacheMongoEntityClassVisitor(String className, ClassVisitor outputClassVisitor, + MetamodelInfo> modelInfo, ClassInfo panacheEntityBaseClassInfo, + ClassInfo entityInfo) { + super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo); + } + + @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/MutinyPanacheMongoRepositoryEnhancer.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/MutinyPanacheMongoRepositoryEnhancer.java new file mode 100644 index 0000000000000..af5c16b6fea77 --- /dev/null +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/MutinyPanacheMongoRepositoryEnhancer.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.reactive.ReactivePanacheMongoRepository; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepositoryBase; +import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; + +public class MutinyPanacheMongoRepositoryEnhancer extends PanacheRepositoryEnhancer { + public static final DotName PANACHE_REPOSITORY_BASE_NAME = DotName + .createSimple(ReactivePanacheMongoRepositoryBase.class.getName()); + + public static final DotName PANACHE_REPOSITORY_NAME = DotName.createSimple(ReactivePanacheMongoRepository.class.getName()); + + public MutinyPanacheMongoRepositoryEnhancer(IndexView index) { + super(index, PanacheResourceProcessor.DOTNAME_MUTINY_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 MutinyPanacheMongoEntityEnhancer.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/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 3de1f025f7a07..49d6ae16457a5 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 @@ -72,6 +72,16 @@ public class PanacheResourceProcessor { .createSimple(ReactivePanacheMongoEntityBase.class.getName()); private static final DotName DOTNAME_AXLE_PANACHE_ENTITY = DotName.createSimple(ReactivePanacheMongoEntity.class.getName()); + // reactive types: Mutiny + static final DotName DOTNAME_MUTINY_PANACHE_REPOSITORY_BASE = DotName + .createSimple(io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepositoryBase.class.getName()); + private static final DotName DOTNAME_MUTINY_PANACHE_REPOSITORY = DotName + .createSimple(io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepository.class.getName()); + static final DotName DOTNAME_MUTINY_PANACHE_ENTITY_BASE = DotName + .createSimple(io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntityBase.class.getName()); + private static final DotName DOTNAME_MUTINY_PANACHE_ENTITY = DotName + .createSimple(io.quarkus.mongodb.panache.reactive.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()); @@ -352,4 +362,61 @@ void buildAxle(CombinedIndexBuildItem index, } } } + + @BuildStep + void buildMutiny(CombinedIndexBuildItem index, + ApplicationIndexBuildItem applicationIndex, + BuildProducer transformers) { + + MutinyPanacheMongoRepositoryEnhancer daoEnhancer = new MutinyPanacheMongoRepositoryEnhancer(index.getIndex()); + Set daoClasses = new HashSet<>(); + for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_MUTINY_PANACHE_REPOSITORY_BASE)) { + // Skip PanacheRepository + if (classInfo.name().equals(DOTNAME_MUTINY_PANACHE_REPOSITORY)) + continue; + if (PanacheRepositoryEnhancer.skipRepository(classInfo)) + continue; + daoClasses.add(classInfo.name().toString()); + } + for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_MUTINY_PANACHE_REPOSITORY)) { + if (PanacheRepositoryEnhancer.skipRepository(classInfo)) + continue; + daoClasses.add(classInfo.name().toString()); + } + for (String daoClass : daoClasses) { + System.out.println("found " + daoClasses); + transformers.produce(new BytecodeTransformerBuildItem(daoClass, daoEnhancer)); + } + + System.out.println("HERE WE ARE"); + + MutinyPanacheMongoEntityEnhancer modelEnhancer = new MutinyPanacheMongoEntityEnhancer(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_MUTINY_PANACHE_ENTITY_BASE)) { + if (classInfo.name().equals(DOTNAME_MUTINY_PANACHE_ENTITY)) + continue; + if (modelClasses.add(classInfo.name().toString())) + modelEnhancer.collectFields(classInfo); + } + for (ClassInfo classInfo : index.getIndex().getAllKnownSubclasses(DOTNAME_MUTINY_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)); + } + } + } + } } From 12616d172958bcb59bda1e914edfd1d4e52ee382 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Fri, 21 Feb 2020 17:19:31 +0100 Subject: [PATCH 4/5] Deprecate Axle based classes. --- .../mongodb/panache/axle/ReactivePanacheMongoEntity.java | 2 ++ .../mongodb/panache/axle/ReactivePanacheMongoEntityBase.java | 2 ++ .../mongodb/panache/axle/ReactivePanacheMongoRepository.java | 2 ++ .../panache/axle/ReactivePanacheMongoRepositoryBase.java | 2 ++ .../io/quarkus/mongodb/panache/axle/ReactivePanacheQuery.java | 2 ++ 5 files changed, 10 insertions(+) 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 index 6218b3ba67b7a..6f39fc54cfdad 100644 --- 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 @@ -12,7 +12,9 @@ * all the useful methods. * * @see ReactivePanacheMongoEntityBase + * @deprecated Use {@link io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepository} instead. */ +@Deprecated public abstract class ReactivePanacheMongoEntity extends ReactivePanacheMongoEntityBase { /** 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 index 59367d60f8287..19b8144abc80e 100644 --- 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 @@ -23,7 +23,9 @@ * {@link ReactivePanacheMongoEntity} instead. * * @see ReactivePanacheMongoEntity + * @deprecated Use {@link io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntityBase} instead. */ +@Deprecated public abstract class ReactivePanacheMongoEntityBase { // Operations 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 index e64de1e7a0554..f1580dbdb5f5b 100644 --- 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 @@ -9,7 +9,9 @@ * implement {@link ReactivePanacheMongoRepositoryBase} instead. * * @param The type of entity to operate on + * @deprecated Use {@link io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepository} instead. */ +@Deprecated 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 index 61db1b7959b43..352ca96d02881 100644 --- 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 @@ -25,7 +25,9 @@ * @param The type of entity to operate on * @param The ID type of the entity * @see ReactivePanacheMongoRepository + * @deprecated Use {@link io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntity} instead. */ +@Deprecated public interface ReactivePanacheMongoRepositoryBase { // Operations 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 index 5cf719da92d34..329c617f06a06 100644 --- 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 @@ -17,7 +17,9 @@ * modified, and instances of this interface can be reused to obtain multiple pages of results. * * @param The entity type being queried + * @deprecated Use {@link io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery} instead. */ +@Deprecated public interface ReactivePanacheQuery { // Builder From badf83a15f65875ff4730bb2b79c4ceea7da2396 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Fri, 21 Feb 2020 17:19:53 +0100 Subject: [PATCH 5/5] Extend ITs to cover the Mutiny repositories and entities --- .../deployment/PanacheResourceProcessor.java | 3 - integration-tests/mongodb-panache/pom.xml | 8 + .../reactive/book/ReactiveBookEntity.java | 81 +++++ .../book/ReactiveBookEntityResource.java | 135 +++++++ .../reactive/book/ReactiveBookRepository.java | 10 + .../book/ReactiveBookRepositoryResource.java | 129 +++++++ .../reactive/person/ReactivePersonEntity.java | 12 + .../person/ReactivePersonEntityResource.java | 75 ++++ .../person/ReactivePersonRepository.java | 10 + .../ReactivePersonRepositoryResource.java | 81 +++++ .../NativeMongodbPanacheResourceIT.java | 8 + .../ReactiveMongodbPanacheResourceTest.java | 334 ++++++++++++++++++ 12 files changed, 883 insertions(+), 3 deletions(-) create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntity.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntityResource.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepository.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepositoryResource.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntity.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntityResource.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepository.java create mode 100644 integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepositoryResource.java create mode 100644 integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/NativeMongodbPanacheResourceIT.java create mode 100644 integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/ReactiveMongodbPanacheResourceTest.java 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 49d6ae16457a5..1f83fe5b593fb 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 @@ -384,12 +384,9 @@ void buildMutiny(CombinedIndexBuildItem index, daoClasses.add(classInfo.name().toString()); } for (String daoClass : daoClasses) { - System.out.println("found " + daoClasses); transformers.produce(new BytecodeTransformerBuildItem(daoClass, daoEnhancer)); } - System.out.println("HERE WE ARE"); - MutinyPanacheMongoEntityEnhancer modelEnhancer = new MutinyPanacheMongoEntityEnhancer(index.getIndex()); Set modelClasses = new HashSet<>(); // Note that we do this in two passes because for some reason Jandex does not give us subtypes diff --git a/integration-tests/mongodb-panache/pom.xml b/integration-tests/mongodb-panache/pom.xml index 875eb656f078c..85585f48a28c1 100755 --- a/integration-tests/mongodb-panache/pom.xml +++ b/integration-tests/mongodb-panache/pom.xml @@ -27,6 +27,14 @@ io.quarkus quarkus-rest-client + + io.quarkus + quarkus-resteasy-mutiny + + + io.quarkus + quarkus-smallrye-context-propagation + io.quarkus diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntity.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntity.java new file mode 100644 index 0000000000000..1f83216437968 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntity.java @@ -0,0 +1,81 @@ +package io.quarkus.it.mongodb.panache.reactive.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.reactive.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/reactive/book/ReactiveBookEntityResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntityResource.java new file mode 100644 index 0000000000000..cb4709d236117 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntityResource.java @@ -0,0 +1,135 @@ +package io.quarkus.it.mongodb.panache.reactive.book; + +import java.net.URI; +import java.time.LocalDate; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PATCH; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +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.mongodb.panache.reactive.ReactivePanacheMongoEntityBase; +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; +import io.smallrye.mutiny.Uni; + +@Path("/reactive/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 Uni> 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 Uni addBook(ReactiveBookEntity book) { + return book.persist().map(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 Uni updateBook(ReactiveBookEntity book) { + return book.update().map(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public Uni upsertBook(ReactiveBookEntity book) { + return book.persistOrUpdate().map(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public Uni deleteBook(@PathParam("id") String id) { + return ReactiveBookEntity.findById(new ObjectId(id)).flatMap(ReactivePanacheMongoEntityBase::delete); + } + + @GET + @Path("/{id}") + public Uni getBook(@PathParam("id") String id) { + return ReactiveBookEntity.findById(new ObjectId(id)); + } + + @GET + @Path("/search/{author}") + public Uni> getBooksByAuthor(@PathParam("author") String author) { + return ReactiveBookEntity.list("author", author); + } + + @GET + @Path("/search") + public Uni 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 Uni 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(); + } + + @DELETE + public Uni deleteAll() { + return ReactiveBookEntity.deleteAll().map(l -> null); + } + +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepository.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepository.java new file mode 100644 index 0000000000000..7cb283d7ca032 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepository.java @@ -0,0 +1,10 @@ +package io.quarkus.it.mongodb.panache.reactive.book; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.it.mongodb.panache.book.Book; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepository; + +@ApplicationScoped +public class ReactiveBookRepository implements ReactivePanacheMongoRepository { +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepositoryResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepositoryResource.java new file mode 100644 index 0000000000000..f411ec6092985 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepositoryResource.java @@ -0,0 +1,129 @@ +package io.quarkus.it.mongodb.panache.reactive.book; + +import java.net.URI; +import java.time.LocalDate; +import java.util.List; + +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; +import io.smallrye.mutiny.Uni; + +@Path("/reactive/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 Uni> 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 Uni addBook(Book book) { + return reactiveBookRepository.persist(book).map(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 Uni updateBook(Book book) { + return reactiveBookRepository.update(book).map(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public Uni upsertBook(Book book) { + return reactiveBookRepository.persistOrUpdate(book).map(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public Uni deleteBook(@PathParam("id") String id) { + return reactiveBookRepository.findById(new ObjectId(id)) + .flatMap(book -> reactiveBookRepository.delete(book)); + } + + @GET + @Path("/{id}") + public Uni getBook(@PathParam("id") String id) { + return reactiveBookRepository.findById(new ObjectId(id)); + } + + @GET + @Path("/search/{author}") + public Uni> getBooksByAuthor(@PathParam("author") String author) { + return reactiveBookRepository.list("author", author); + } + + @GET + @Path("/search") + public Uni 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 Uni 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(); + } + + @DELETE + public Uni deleteAll() { + return reactiveBookRepository.deleteAll().map(l -> null); + } +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntity.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntity.java new file mode 100644 index 0000000000000..e389795a2de8e --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntity.java @@ -0,0 +1,12 @@ +package io.quarkus.it.mongodb.panache.reactive.person; + +import org.bson.codecs.pojo.annotations.BsonId; + +import io.quarkus.mongodb.panache.reactive.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/reactive/person/ReactivePersonEntityResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntityResource.java new file mode 100644 index 0000000000000..b482c71b7d5d5 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntityResource.java @@ -0,0 +1,75 @@ +package io.quarkus.it.mongodb.panache.reactive.person; + +import java.net.URI; +import java.util.List; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntityBase; +import io.quarkus.panache.common.Sort; +import io.smallrye.mutiny.Uni; + +@Path("/reactive/persons/entity") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ReactivePersonEntityResource { + @GET + public Uni> getPersons(@QueryParam("sort") String sort) { + if (sort != null) { + return ReactivePersonEntity.listAll(Sort.ascending(sort)); + } + return ReactivePersonEntity.listAll(); + } + + @POST + public Uni addPerson(ReactivePersonEntity person) { + return person.persist().map(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 Uni addPersons(List persons) { + return ReactivePersonEntity.persist(persons); + } + + @PUT + public Uni updatePerson(ReactivePersonEntity person) { + return person.update().map(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public Uni upsertPerson(ReactivePersonEntity person) { + return person.persistOrUpdate().map(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public Uni deletePerson(@PathParam("id") String id) { + return ReactivePersonEntity.findById(Long.parseLong(id)).flatMap(ReactivePanacheMongoEntityBase::delete); + } + + @GET + @Path("/{id}") + public Uni getPerson(@PathParam("id") String id) { + return ReactivePersonEntity.findById(Long.parseLong(id)); + } + + @GET + @Path("/count") + public Uni countAll() { + return ReactivePersonEntity.count(); + } + + @DELETE + public Uni deleteAll() { + return ReactivePersonEntity.deleteAll() + .onItem().ignore().andContinueWithNull(); + } +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepository.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepository.java new file mode 100644 index 0000000000000..01054e26b910a --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepository.java @@ -0,0 +1,10 @@ +package io.quarkus.it.mongodb.panache.reactive.person; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.it.mongodb.panache.person.Person; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepositoryBase; + +@ApplicationScoped +public class ReactivePersonRepository implements ReactivePanacheMongoRepositoryBase { +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepositoryResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepositoryResource.java new file mode 100644 index 0000000000000..00f767991c4ae --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepositoryResource.java @@ -0,0 +1,81 @@ +package io.quarkus.it.mongodb.panache.reactive.person; + +import java.net.URI; +import java.util.List; + +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; +import io.smallrye.mutiny.Uni; + +@Path("/reactive/persons/repository") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ReactivePersonRepositoryResource { + + @Inject + ReactivePersonRepository reactivePersonRepository; + + @GET + public Uni> getPersons(@QueryParam("sort") String sort) { + if (sort != null) { + return reactivePersonRepository.listAll(Sort.ascending(sort)); + } + return reactivePersonRepository.listAll(); + } + + @POST + public Uni addPerson(Person person) { + return reactivePersonRepository.persist(person).map(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 Uni addPersons(List persons) { + return reactivePersonRepository.persist(persons); + } + + @PUT + public Uni updatePerson(Person person) { + return reactivePersonRepository.update(person).map(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public Uni upsertPerson(Person person) { + return reactivePersonRepository.persistOrUpdate(person).map(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public Uni deletePerson(@PathParam("id") String id) { + return reactivePersonRepository.findById(Long.parseLong(id)) + .flatMap(person -> reactivePersonRepository.delete(person)); + } + + @GET + @Path("/{id}") + public Uni getPerson(@PathParam("id") String id) { + return reactivePersonRepository.findById(Long.parseLong(id)); + } + + @GET + @Path("/count") + public Uni countAll() { + return reactivePersonRepository.count(); + } + + @DELETE + public Uni deleteAll() { + return reactivePersonRepository.deleteAll() + .onItem().ignore().andContinueWithNull(); + } +} diff --git a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/NativeMongodbPanacheResourceIT.java b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/NativeMongodbPanacheResourceIT.java new file mode 100644 index 0000000000000..729ca8be6771e --- /dev/null +++ b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/NativeMongodbPanacheResourceIT.java @@ -0,0 +1,8 @@ +package io.quarkus.it.mongodb.panache.reactive; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class NativeMongodbPanacheResourceIT extends ReactiveMongodbPanacheResourceTest { + +} diff --git a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/ReactiveMongodbPanacheResourceTest.java b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/ReactiveMongodbPanacheResourceTest.java new file mode 100644 index 0000000000000..3a2bcbc443ded --- /dev/null +++ b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/ReactiveMongodbPanacheResourceTest.java @@ -0,0 +1,334 @@ +package io.quarkus.it.mongodb.panache.reactive; + +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.util.*; + +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.Assertions; +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 io.quarkus.it.mongodb.panache.BookDTO; +import io.quarkus.it.mongodb.panache.MongoTestResource; +import io.quarkus.it.mongodb.panache.book.BookDetail; +import io.quarkus.it.mongodb.panache.person.Person; +import io.quarkus.test.common.QuarkusTestResource; +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 +@QuarkusTestResource(MongoTestResource.class) +class ReactiveMongodbPanacheResourceTest { + private static final Logger LOGGER = LoggerFactory.getLogger( + ReactiveMongodbPanacheResourceTest.class); + private static final TypeRef> LIST_OF_BOOK_TYPE_REF = new TypeRef>() { + }; + private static final TypeRef> LIST_OF_PERSON_TYPE_REF = new TypeRef>() { + }; + + @Test + public void testReactiveBookEntity() throws InterruptedException { + callReactiveBookEndpoint("/reactive/books/entity"); + } + + @Test + public void testReactiveBookRepository() throws InterruptedException { + callReactiveBookEndpoint("/reactive/books/repository"); + } + + @Test + public void testReactivePersonEntity() { + callReactivePersonEndpoint("/reactive/persons/entity"); + } + + @Test + public void testReactivePersonRepository() { + callReactivePersonEndpoint("/reactive/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()); + } + + //delete all + response = RestAssured + .given() + .delete(endpoint) + .andReturn(); + Assertions.assertEquals(204, response.statusCode()); + } + + 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 class IntegerAdder { + int cpt = 0; + + public void increment() { + cpt++; + } + + public int count() { + return cpt; + } + } +}