diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 93166305a5bb9..381fc3ebdcb4b 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -639,6 +639,11 @@ quarkus-panache-common-deployment ${project.version} + + io.quarkus + quarkus-mongodb-panache-common-deployment + ${project.version} + io.quarkus quarkus-panache-mock @@ -709,6 +714,16 @@ quarkus-mongodb-panache-deployment ${project.version} + + io.quarkus + quarkus-mongodb-panache-kotlin + ${project.version} + + + io.quarkus + quarkus-mongodb-panache-common + ${project.version} + io.quarkus quarkus-hibernate-search-elasticsearch diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java index f2672f2ec5ea6..dc1263da2918b 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/Capability.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/Capability.java @@ -31,6 +31,7 @@ public enum Capability { JWT, TIKA, MONGODB_PANACHE, + MONGODB_PANACHE_KOTLIN, FLYWAY, LIQUIBASE, SECURITY, diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java index 2f1c3145eb830..90461bc4d0ee1 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java @@ -10,6 +10,7 @@ import static io.quarkus.deployment.util.ReflectUtil.rawTypeIs; import static io.quarkus.deployment.util.ReflectUtil.rawTypeOf; import static io.quarkus.deployment.util.ReflectUtil.rawTypeOfParameter; +import static java.util.Arrays.asList; import java.io.IOException; import java.lang.reflect.AnnotatedElement; @@ -253,6 +254,9 @@ public static Consumer loadStepsFrom(Class clazz, BuildTim final Constructor[] constructors = clazz.getDeclaredConstructors(); // this is the chain configuration that will contain all steps on this class and be returned Consumer chainConfig = Functions.discardingConsumer(); + if (Modifier.isAbstract(clazz.getModifiers())) { + return chainConfig; + } // this is the step configuration that applies to all steps on this class Consumer stepConfig = Functions.discardingConsumer(); // this is the build step instance setup that applies to all steps on this class @@ -482,7 +486,7 @@ public static Consumer loadStepsFrom(Class clazz, BuildTim } // now iterate the methods - final Method[] methods = clazz.getDeclaredMethods(); + final List methods = getMethods(clazz); for (Method method : methods) { final int mods = method.getModifiers(); if (Modifier.isStatic(mods)) { @@ -963,6 +967,15 @@ public String toString() { return chainConfig; } + protected static List getMethods(Class clazz) { + List declaredMethods = new ArrayList<>(); + if (!clazz.getName().equals(Object.class.getName())) { + declaredMethods.addAll(getMethods(clazz.getSuperclass())); + declaredMethods.addAll(asList(clazz.getDeclaredMethods())); + } + return declaredMethods; + } + private static BooleanSupplier and(BooleanSupplier a, BooleanSupplier b) { return () -> a.getAsBoolean() && b.getAsBoolean(); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/Feature.java b/core/deployment/src/main/java/io/quarkus/deployment/Feature.java index 56bab5e6a90d5..843cd31d9f4fd 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/Feature.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/Feature.java @@ -59,6 +59,7 @@ public enum Feature { MAILER, MONGODB_CLIENT, MONGODB_PANACHE, + MONGODB_PANACHE_KOTLIN, MUTINY, NARAYANA_JTA, NARAYANA_STM, diff --git a/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java b/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java index b1d43433b0780..c72b9c9d88ba5 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/util/AsmUtil.java @@ -1,7 +1,20 @@ package io.quarkus.deployment.util; +import static java.util.Arrays.asList; +import static org.objectweb.asm.Type.BOOLEAN_TYPE; +import static org.objectweb.asm.Type.BYTE_TYPE; +import static org.objectweb.asm.Type.CHAR_TYPE; +import static org.objectweb.asm.Type.FLOAT_TYPE; +import static org.objectweb.asm.Type.INT_TYPE; +import static org.objectweb.asm.Type.LONG_TYPE; +import static org.objectweb.asm.Type.SHORT_TYPE; +import static org.objectweb.asm.Type.VOID_TYPE; +import static org.objectweb.asm.Type.getType; + import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Function; import org.jboss.jandex.ArrayType; @@ -20,6 +33,36 @@ */ public class AsmUtil { + public static final List PRIMITIVES = asList( + VOID_TYPE, + BOOLEAN_TYPE, + CHAR_TYPE, + BYTE_TYPE, + SHORT_TYPE, + INT_TYPE, + FLOAT_TYPE, + LONG_TYPE); + public static final List WRAPPERS = asList( + getType(Void.class), + getType(Boolean.class), + getType(Character.class), + getType(Byte.class), + getType(Short.class), + getType(Integer.class), + getType(Float.class), + getType(Long.class)); + public static final Map WRAPPER_TO_PRIMITIVE = new HashMap<>(); + + static { + for (int i = 0; i < AsmUtil.PRIMITIVES.size(); i++) { + AsmUtil.WRAPPER_TO_PRIMITIVE.put(AsmUtil.WRAPPERS.get(i), AsmUtil.PRIMITIVES.get(i)); + } + } + + public static org.objectweb.asm.Type autobox(org.objectweb.asm.Type primitive) { + return WRAPPERS.get(primitive.getSort()); + } + /** * Returns the Java bytecode signature of a given Jandex MethodInfo using the given type argument mappings. * For example, given this method: @@ -431,6 +474,43 @@ public static void unboxIfRequired(MethodVisitor mv, Type jandexType) { } } + /** + * Calls the right unboxing method for the given Jandex Type if it is a primitive. + * + * @param mv The MethodVisitor on which to visit the unboxing instructions + * @param type The Jandex Type to unbox if it is a primitive. + */ + public static void unboxIfRequired(MethodVisitor mv, org.objectweb.asm.Type type) { + if (type.getSort() <= org.objectweb.asm.Type.DOUBLE) { + switch (type.getSort()) { + case org.objectweb.asm.Type.BOOLEAN: + unbox(mv, "java/lang/Boolean", "booleanValue", "Z"); + break; + case org.objectweb.asm.Type.BYTE: + unbox(mv, "java/lang/Byte", "byteValue", "B"); + break; + case org.objectweb.asm.Type.CHAR: + unbox(mv, "java/lang/Character", "charValue", "C"); + break; + case org.objectweb.asm.Type.DOUBLE: + unbox(mv, "java/lang/Double", "doubleValue", "D"); + break; + case org.objectweb.asm.Type.FLOAT: + unbox(mv, "java/lang/Float", "floatValue", "F"); + break; + case org.objectweb.asm.Type.INT: + unbox(mv, "java/lang/Integer", "intValue", "I"); + break; + case org.objectweb.asm.Type.LONG: + unbox(mv, "java/lang/Long", "longValue", "J"); + break; + case org.objectweb.asm.Type.SHORT: + unbox(mv, "java/lang/Short", "shortValue", "S"); + break; + } + } + } + private static void unbox(MethodVisitor mv, String owner, String methodName, String returnTypeSignature) { mv.visitTypeInsn(Opcodes.CHECKCAST, owner); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, methodName, "()" + returnTypeSignature, false); diff --git a/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/reactive/MongoWithReplicasTestBase.java b/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/reactive/MongoWithReplicasTestBase.java index 23561d8a5c24c..f9aa3a09c5589 100644 --- a/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/reactive/MongoWithReplicasTestBase.java +++ b/extensions/mongodb-client/runtime/src/test/java/io/quarkus/mongodb/reactive/MongoWithReplicasTestBase.java @@ -108,10 +108,10 @@ protected String getConnectionString() { } private static void initializeReplicaSet(final List mongodConfigList) throws UnknownHostException { - final String arbitrerAddress = "mongodb://" + mongodConfigList.get(0).net().getServerAddress().getHostName() + ":" + final String arbiterAddress = "mongodb://" + mongodConfigList.get(0).net().getServerAddress().getHostName() + ":" + mongodConfigList.get(0).net().getPort(); final MongoClientSettings mo = MongoClientSettings.builder() - .applyConnectionString(new ConnectionString(arbitrerAddress)).build(); + .applyConnectionString(new ConnectionString(arbiterAddress)).build(); try (MongoClient mongo = MongoClients.create(mo)) { final MongoDatabase mongoAdminDB = mongo.getDatabase("admin"); diff --git a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheCompanionEnhancer.java b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheCompanionEnhancer.java index 620bb5cb771bb..a8e4585184f37 100644 --- a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheCompanionEnhancer.java +++ b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheCompanionEnhancer.java @@ -23,7 +23,7 @@ public class KotlinPanacheCompanionEnhancer extends PanacheEntityEnhancer methodCustomizers) { - super(index, KotlinPanacheResourceProcessor.PANACHE_ENTITY_BASE_DOTNAME, methodCustomizers); + super(index, methodCustomizers); modelInfo = new MetamodelInfo<>(); } diff --git a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheEntityClassVisitor.java b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheEntityClassVisitor.java index 49df9ed685726..74b478f1a039a 100644 --- a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheEntityClassVisitor.java +++ b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheEntityClassVisitor.java @@ -13,18 +13,17 @@ 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; import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; +import io.quarkus.panache.common.deployment.visitors.PanacheEntityClassVisitor; /** * This visitor process kotlin entities and removes the final modifier from the compiler generated getters and setters. * Unlike Java entities, we don't need to generate the getters and setters because kotlinc does that for us but kotlin, * by default, is final so we need to open those up for hibernate to add its hooks. */ -class KotlinPanacheEntityClassVisitor extends PanacheEntityEnhancer.PanacheEntityClassVisitor { +class KotlinPanacheEntityClassVisitor extends PanacheEntityClassVisitor { private String entityBinaryType; - private org.objectweb.asm.Type entityType; public KotlinPanacheEntityClassVisitor(String className, ClassVisitor outputClassVisitor, MetamodelInfo> modelInfo, @@ -38,8 +37,7 @@ public KotlinPanacheEntityClassVisitor(String className, ClassVisitor outputClas public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces); - entityBinaryType = name.replace('.', '/'); - entityType = org.objectweb.asm.Type.getType("L" + entityBinaryType + ";"); + entityBinaryType = name; } @Override @@ -52,20 +50,10 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str } @Override - protected String getModelDescriptor() { - return "Ljava/lang/Class;"; - } - - @Override - protected String getPanacheOperationsBinaryName() { + protected String getPanacheOperationsInternalName() { return KotlinPanacheEntityEnhancer.JPA_OPERATIONS_BINARY_NAME; } - @Override - protected void injectModel(MethodVisitor mv) { - mv.visitLdcInsn(entityType); - } - @Override protected void generateAccessorSetField(MethodVisitor mv, EntityField field) { mv.visitMethodInsn( diff --git a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheEntityEnhancer.java b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheEntityEnhancer.java index bd682866d26bd..f976635ad80ef 100644 --- a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheEntityEnhancer.java +++ b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheEntityEnhancer.java @@ -1,10 +1,11 @@ package io.quarkus.hibernate.orm.panache.kotlin.deployment; +import static io.quarkus.hibernate.orm.panache.kotlin.deployment.KotlinPanacheResourceProcessor.PANACHE_ENTITY_BASE; +import static io.quarkus.hibernate.orm.panache.kotlin.deployment.KotlinPanacheResourceProcessor.TRANSIENT; + import java.lang.reflect.Modifier; import java.util.List; -import javax.persistence.Transient; - import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.FieldInfo; @@ -24,16 +25,15 @@ public class KotlinPanacheEntityEnhancer extends PanacheEntityEnhancer methodCustomizers) { - super(index, KotlinPanacheResourceProcessor.PANACHE_ENTITY_BASE_DOTNAME, methodCustomizers); + super(index, methodCustomizers); modelInfo = new MetamodelInfo<>(); } @Override public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { - return new KotlinPanacheEntityClassVisitor(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, + return new KotlinPanacheEntityClassVisitor(className, outputClassVisitor, modelInfo, + indexView.getClassByName(PANACHE_ENTITY_BASE), indexView.getClassByName(DotName.createSimple(className)), methodCustomizers); } @@ -43,7 +43,7 @@ public void collectFields(ClassInfo classInfo) { String name = fieldInfo.name(); if (Modifier.isPublic(fieldInfo.flags()) && !Modifier.isStatic(fieldInfo.flags()) - && !fieldInfo.hasAnnotation(DOTNAME_TRANSIENT)) { + && !fieldInfo.hasAnnotation(TRANSIENT)) { entityModel.addField(new EntityField(name, DescriptorUtils.typeToString(fieldInfo.type()))); } } diff --git a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheRepositoryClassVisitor.java b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheRepositoryClassVisitor.java index 674bf0ba3a44f..6fd8e6b5b4a4b 100644 --- a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheRepositoryClassVisitor.java +++ b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheRepositoryClassVisitor.java @@ -1,11 +1,11 @@ package io.quarkus.hibernate.orm.panache.kotlin.deployment; +import static io.quarkus.deployment.util.AsmUtil.autobox; import static io.quarkus.gizmo.Gizmo.ASM_API_VERSION; import static io.quarkus.hibernate.orm.panache.kotlin.deployment.KotlinPanacheResourceProcessor.JPA_OPERATIONS; -import static io.quarkus.hibernate.orm.panache.kotlin.deployment.KotlinPanacheResourceProcessor.PANACHE_REPOSITORY_BASE_DOTNAME; -import static io.quarkus.hibernate.orm.panache.kotlin.deployment.KotlinPanacheResourceProcessor.autobox; +import static io.quarkus.hibernate.orm.panache.kotlin.deployment.KotlinPanacheResourceProcessor.PANACHE_REPOSITORY_BASE; import static io.quarkus.hibernate.orm.panache.kotlin.deployment.KotlinPanacheResourceProcessor.sanitize; -import static io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer.PanacheRepositoryClassVisitor.findEntityTypeArgumentsForPanacheRepository; +import static io.quarkus.panache.common.deployment.visitors.PanacheRepositoryClassVisitor.findEntityTypeArgumentsForPanacheRepository; import static org.jboss.jandex.DotName.createSimple; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.INVOKESTATIC; @@ -57,7 +57,7 @@ public void visit(int version, int access, String name, String signature, String final String repositoryClassName = name.replace('/', '.'); String[] foundTypeArguments = findEntityTypeArgumentsForPanacheRepository(indexView, repositoryClassName, - PANACHE_REPOSITORY_BASE_DOTNAME); + PANACHE_REPOSITORY_BASE); String entityBinaryType = foundTypeArguments[0]; entitySignature = "L" + entityBinaryType + ";"; diff --git a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheRepositoryEnhancer.java b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheRepositoryEnhancer.java index 003d256378604..53e58e4f21d47 100644 --- a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheRepositoryEnhancer.java +++ b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheRepositoryEnhancer.java @@ -8,7 +8,7 @@ public class KotlinPanacheRepositoryEnhancer extends PanacheRepositoryEnhancer { public KotlinPanacheRepositoryEnhancer(IndexView index) { - super(index, KotlinPanacheResourceProcessor.PANACHE_REPOSITORY_BASE_DOTNAME); + super(index, KotlinPanacheResourceProcessor.PANACHE_REPOSITORY_BASE); } @Override diff --git a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheResourceProcessor.java b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheResourceProcessor.java index f463cca99af13..a3b4e29795ca3 100644 --- a/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheResourceProcessor.java +++ b/extensions/panache/hibernate-orm-panache-kotlin/deployment/src/main/java/io/quarkus/hibernate/orm/panache/kotlin/deployment/KotlinPanacheResourceProcessor.java @@ -11,6 +11,7 @@ import java.util.stream.Collectors; import javax.persistence.EntityManager; +import javax.persistence.Transient; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; @@ -40,7 +41,6 @@ import io.quarkus.panache.common.deployment.PanacheFieldAccessEnhancer; import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem; -import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; public final class KotlinPanacheResourceProcessor { @@ -49,12 +49,12 @@ public final class KotlinPanacheResourceProcessor { static final String JPA_OPERATIONS = createSimple(JpaOperations.class.getName()) .toString().replace(".", "/"); - static final DotName PANACHE_REPOSITORY_BASE_DOTNAME = createSimple(PanacheRepositoryBase.class.getName()); - static final DotName PANACHE_REPOSITORY_DOTNAME = createSimple(PanacheRepository.class.getName()); + static final DotName PANACHE_REPOSITORY_BASE = createSimple(PanacheRepositoryBase.class.getName()); + static final DotName PANACHE_REPOSITORY = createSimple(PanacheRepository.class.getName()); - static final DotName PANACHE_ENTITY_BASE_DOTNAME = createSimple(PanacheEntityBase.class.getName()); - static final DotName PANACHE_ENTITY_DOTNAME = createSimple(PanacheEntity.class.getName()); - static final DotName PANACHE_COMPANION_DOTNAME = createSimple(PanacheCompanion.class.getName()); + static final DotName PANACHE_ENTITY_BASE = createSimple(PanacheEntityBase.class.getName()); + static final DotName PANACHE_ENTITY = createSimple(PanacheEntity.class.getName()); + static final DotName PANACHE_COMPANION = createSimple(PanacheCompanion.class.getName()); static final String PANACHE_ENTITY_BASE_SIGNATURE = toBinarySignature(PanacheEntityBase.class); static final String PANACHE_ENTITY_SIGNATURE = toBinarySignature(PanacheEntity.class); @@ -62,6 +62,7 @@ public final class KotlinPanacheResourceProcessor { static final String OBJECT_SIGNATURE = toBinarySignature(Object.class); static final String CLASS_SIGNATURE = toBinarySignature(Class.class); + static final DotName TRANSIENT = DotName.createSimple(Transient.class.getName()); static final org.objectweb.asm.Type CLASS_TYPE = org.objectweb.asm.Type.getType(Class.class); static final org.objectweb.asm.Type OBJECT_TYPE = org.objectweb.asm.Type.getType(Object.class); @@ -72,15 +73,6 @@ public final class KotlinPanacheResourceProcessor { org.objectweb.asm.Type.getType(PanacheEntityBase.class), org.objectweb.asm.Type.getType(PanacheEntity.class), org.objectweb.asm.Type.getType(PanacheCompanion.class)); - public static final List PRIMITIVE_TYPES = asList( - org.objectweb.asm.Type.getType(Void.class), - org.objectweb.asm.Type.getType(Boolean.class), - org.objectweb.asm.Type.getType(Character.class), - org.objectweb.asm.Type.getType(Byte.class), - org.objectweb.asm.Type.getType(Short.class), - org.objectweb.asm.Type.getType(Integer.class), - org.objectweb.asm.Type.getType(Float.class), - org.objectweb.asm.Type.getType(Long.class)); static org.objectweb.asm.Type sanitize(org.objectweb.asm.Type[] argumentTypes) { org.objectweb.asm.Type primitiveReplaced = null; @@ -98,10 +90,6 @@ static org.objectweb.asm.Type sanitize(org.objectweb.asm.Type[] argumentTypes) { return primitiveReplaced; } - static org.objectweb.asm.Type autobox(org.objectweb.asm.Type primitive) { - return PRIMITIVE_TYPES.get(primitive.getSort()); - } - @NotNull static String toBinarySignature(Class type) { return org.objectweb.asm.Type.getType(type).getDescriptor(); @@ -147,16 +135,16 @@ void build(CombinedIndexBuildItem index, KotlinPanacheRepositoryEnhancer daoEnhancer = new KotlinPanacheRepositoryEnhancer(index.getIndex()); Set daoClasses = new HashSet<>(); - for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(PANACHE_REPOSITORY_BASE_DOTNAME)) { + for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(PANACHE_REPOSITORY_BASE)) { // Skip PanacheRepository - if (classInfo.name().equals(PANACHE_REPOSITORY_DOTNAME)) + if (classInfo.name().equals(PANACHE_REPOSITORY)) continue; - if (PanacheRepositoryEnhancer.skipRepository(classInfo)) + if (daoEnhancer.skipRepository(classInfo)) continue; daoClasses.add(classInfo.name().toString()); } - for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(PANACHE_REPOSITORY_DOTNAME)) { - if (PanacheRepositoryEnhancer.skipRepository(classInfo)) + for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(PANACHE_REPOSITORY)) { + if (daoEnhancer.skipRepository(classInfo)) continue; daoClasses.add(classInfo.name().toString()); } @@ -171,15 +159,15 @@ void build(CombinedIndexBuildItem index, // Note that we do this in two passes because for some reason Jandex does not give us subtypes // of PanacheEntity if we ask for subtypes of PanacheEntityBase // NOTE: we don't skip abstract/generic entities because they still need accessors - for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(PANACHE_COMPANION_DOTNAME)) { - if (classInfo.name().equals(PANACHE_ENTITY_DOTNAME)) + for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(PANACHE_COMPANION)) { + if (classInfo.name().equals(PANACHE_ENTITY)) continue; if (modelClasses.add(classInfo.name().toString())) { transformers.produce(new BytecodeTransformerBuildItem(classInfo.name().toString(), companionEnhancer)); } } - for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(PANACHE_ENTITY_BASE_DOTNAME)) { - if (classInfo.name().equals(PANACHE_ENTITY_DOTNAME)) + for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(PANACHE_ENTITY_BASE)) { + if (classInfo.name().equals(PANACHE_ENTITY)) continue; if (modelClasses.add(classInfo.name().toString())) { entityEnhancer.collectFields(classInfo); diff --git a/extensions/panache/hibernate-orm-panache-kotlin/runtime/pom.xml b/extensions/panache/hibernate-orm-panache-kotlin/runtime/pom.xml index 7b468b79fa036..a9890fa2d479b 100644 --- a/extensions/panache/hibernate-orm-panache-kotlin/runtime/pom.xml +++ b/extensions/panache/hibernate-orm-panache-kotlin/runtime/pom.xml @@ -63,6 +63,7 @@ org.jetbrains.kotlin kotlin-reflect ${kotlin.version} + test org.ow2.asm diff --git a/extensions/panache/hibernate-orm-panache-kotlin/runtime/src/main/kotlin/io/quarkus/hibernate/orm/panache/kotlin/PanacheCompanion.kt b/extensions/panache/hibernate-orm-panache-kotlin/runtime/src/main/kotlin/io/quarkus/hibernate/orm/panache/kotlin/PanacheCompanion.kt index c230798d60e58..beaf6597c9e6c 100644 --- a/extensions/panache/hibernate-orm-panache-kotlin/runtime/src/main/kotlin/io/quarkus/hibernate/orm/panache/kotlin/PanacheCompanion.kt +++ b/extensions/panache/hibernate-orm-panache-kotlin/runtime/src/main/kotlin/io/quarkus/hibernate/orm/panache/kotlin/PanacheCompanion.kt @@ -56,7 +56,7 @@ interface PanacheCompanion { fun find(query: String, vararg params: Any): PanacheQuery = injectionMissing() /** - * Find entities using a query and the given sort options, with optional indexed parameters. + * Find entities using a query and the given sort options with optional indexed parameters. * * @param query a query string * @param sort the sort strategy to use diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheHibernateResourceProcessor.java b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheHibernateResourceProcessor.java index e1c600b6add26..f8b88fad31145 100644 --- a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheHibernateResourceProcessor.java +++ b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheHibernateResourceProcessor.java @@ -40,7 +40,6 @@ import io.quarkus.panache.common.deployment.PanacheFieldAccessEnhancer; import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem; -import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; public final class PanacheHibernateResourceProcessor { @@ -117,12 +116,12 @@ void build(CombinedIndexBuildItem index, // Skip PanacheRepository if (classInfo.name().equals(DOTNAME_PANACHE_REPOSITORY)) continue; - if (PanacheRepositoryEnhancer.skipRepository(classInfo)) + if (daoEnhancer.skipRepository(classInfo)) continue; daoClasses.add(classInfo.name().toString()); } for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_PANACHE_REPOSITORY)) { - if (PanacheRepositoryEnhancer.skipRepository(classInfo)) + if (daoEnhancer.skipRepository(classInfo)) continue; daoClasses.add(classInfo.name().toString()); } diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java index b66738d056c75..df2ee5f216cdc 100644 --- a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java +++ b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaEntityEnhancer.java @@ -24,6 +24,7 @@ import io.quarkus.panache.common.deployment.MetamodelInfo; import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; +import io.quarkus.panache.common.deployment.visitors.PanacheEntityClassVisitor; public class PanacheJpaEntityEnhancer extends PanacheEntityEnhancer>> { @@ -41,13 +42,14 @@ public class PanacheJpaEntityEnhancer extends PanacheEntityEnhancer methodCustomizers) { - super(index, PanacheHibernateResourceProcessor.DOTNAME_PANACHE_ENTITY_BASE, methodCustomizers); + super(index, methodCustomizers); modelInfo = new MetamodelInfo<>(); } @Override public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { - return new PanacheJpaEntityClassVisitor(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, + return new PanacheJpaEntityClassVisitor(className, outputClassVisitor, modelInfo, + indexView.getClassByName(PanacheHibernateResourceProcessor.DOTNAME_PANACHE_ENTITY_BASE), indexView.getClassByName(DotName.createSimple(className)), methodCustomizers); } @@ -72,7 +74,7 @@ protected String getModelDescriptor() { } @Override - protected String getPanacheOperationsBinaryName() { + protected String getPanacheOperationsInternalName() { return JPA_OPERATIONS_BINARY_NAME; } diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaRepositoryEnhancer.java b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaRepositoryEnhancer.java index 16dace472aeba..29ef3e9d485b5 100644 --- a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaRepositoryEnhancer.java +++ b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheJpaRepositoryEnhancer.java @@ -1,14 +1,13 @@ package io.quarkus.hibernate.orm.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.hibernate.orm.panache.PanacheRepository; import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase; import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; +import io.quarkus.panache.common.deployment.visitors.PanacheRepositoryClassVisitor; public class PanacheJpaRepositoryEnhancer extends PanacheRepositoryEnhancer { @@ -22,15 +21,14 @@ public PanacheJpaRepositoryEnhancer(IndexView index) { @Override public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { - return new PanacheJpaRepositoryClassVisitor(className, outputClassVisitor, panacheRepositoryBaseClassInfo, + return new PanacheJpaRepositoryClassVisitor(className, outputClassVisitor, this.indexView); } static class PanacheJpaRepositoryClassVisitor extends PanacheRepositoryClassVisitor { - public PanacheJpaRepositoryClassVisitor(String className, ClassVisitor outputClassVisitor, - ClassInfo panacheRepositoryBaseClassInfo, IndexView indexView) { - super(className, outputClassVisitor, panacheRepositoryBaseClassInfo, indexView); + public PanacheJpaRepositoryClassVisitor(String className, ClassVisitor outputClassVisitor, IndexView indexView) { + super(className, outputClassVisitor, indexView); } @Override @@ -44,19 +42,8 @@ protected DotName getPanacheRepositoryBaseDotName() { } @Override - protected String getPanacheOperationsBinaryName() { + protected String getPanacheOperationsInternalName() { return PanacheJpaEntityEnhancer.JPA_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-common/deployment/pom.xml b/extensions/panache/mongodb-panache-common/deployment/pom.xml new file mode 100644 index 0000000000000..c3c794379512d --- /dev/null +++ b/extensions/panache/mongodb-panache-common/deployment/pom.xml @@ -0,0 +1,67 @@ + + + + quarkus-mongodb-panache-common-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-mongodb-panache-common-deployment + Quarkus - MongoDB with Panache - Common Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-jsonb-spi + + + io.quarkus + quarkus-jackson-spi + + + io.quarkus + quarkus-panache-common-deployment + + + io.quarkus + quarkus-mongodb-client-deployment + + + io.quarkus + quarkus-mongodb-panache-common + + + + io.quarkus + quarkus-junit5-internal + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + + \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/BasePanacheMongoResourceProcessor.java b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/BasePanacheMongoResourceProcessor.java new file mode 100644 index 0000000000000..ed1f5649ef18c --- /dev/null +++ b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/BasePanacheMongoResourceProcessor.java @@ -0,0 +1,384 @@ +package io.quarkus.mongodb.panache.deployment; + +import static io.quarkus.deployment.util.JandexUtil.resolveTypeParameters; +import static org.jboss.jandex.DotName.createSimple; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import org.bson.codecs.pojo.annotations.BsonId; +import org.bson.codecs.pojo.annotations.BsonIgnore; +import org.bson.codecs.pojo.annotations.BsonProperty; +import org.bson.types.ObjectId; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.CompositeIndex; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.Indexer; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; + +import io.quarkus.arc.deployment.ValidationPhaseBuildItem; +import io.quarkus.builder.BuildException; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.bean.JavaBeanUtil; +import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; +import io.quarkus.deployment.builditem.ApplicationIndexBuildItem; +import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; +import io.quarkus.deployment.index.IndexingUtil; +import io.quarkus.deployment.util.JandexUtil; +import io.quarkus.jackson.spi.JacksonModuleBuildItem; +import io.quarkus.jsonb.spi.JsonbDeserializerBuildItem; +import io.quarkus.jsonb.spi.JsonbSerializerBuildItem; +import io.quarkus.mongodb.deployment.MongoClientNameBuildItem; +import io.quarkus.mongodb.deployment.MongoUnremovableClientsBuildItem; +import io.quarkus.mongodb.panache.MongoEntity; +import io.quarkus.mongodb.panache.PanacheMongoRecorder; +import io.quarkus.mongodb.panache.ProjectionFor; +import io.quarkus.mongodb.panache.jackson.ObjectIdDeserializer; +import io.quarkus.mongodb.panache.jackson.ObjectIdSerializer; +import io.quarkus.panache.common.deployment.PanacheEntityClassesBuildItem; +import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem; +import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; + +public abstract class BasePanacheMongoResourceProcessor { + public static final DotName BSON_ID = createSimple(BsonId.class.getName()); + public static final DotName BSON_IGNORE = createSimple(BsonIgnore.class.getName()); + public static final DotName BSON_PROPERTY = createSimple(BsonProperty.class.getName()); + public static final DotName MONGO_ENTITY = createSimple(MongoEntity.class.getName()); + public static final DotName OBJECT_ID = createSimple(ObjectId.class.getName()); + public static final String OBJECT_SIGNATURE = toBinarySignature(Object.class); + public static final DotName PROJECTION_FOR = createSimple(ProjectionFor.class.getName()); + + protected static String toBinarySignature(Class type) { + return org.objectweb.asm.Type.getType(type).getDescriptor(); + } + + @BuildStep + public void buildImperative(CombinedIndexBuildItem index, ApplicationIndexBuildItem applicationIndex, + BuildProducer transformers, + BuildProducer reflectiveClass, + BuildProducer propertyMappingClass, + List methodCustomizersBuildItems) { + + List methodCustomizers = methodCustomizersBuildItems.stream() + .map(bi -> bi.getMethodCustomizer()).collect(Collectors.toList()); + + processTypes(index, transformers, reflectiveClass, propertyMappingClass, getImperativeTypeBundle(), + createRepositoryEnhancer(index), createEntityEnhancer(index, methodCustomizers)); + } + + @BuildStep + public void buildReactive(CombinedIndexBuildItem index, + BuildProducer reflectiveClass, + BuildProducer propertyMappingClass, + BuildProducer transformers, + List methodCustomizersBuildItems) { + List methodCustomizers = methodCustomizersBuildItems.stream() + .map(bi -> bi.getMethodCustomizer()).collect(Collectors.toList()); + + processTypes(index, transformers, reflectiveClass, propertyMappingClass, getReactiveTypeBundle(), + createReactiveRepositoryEnhancer(index), createReactiveEntityEnhancer(index, methodCustomizers)); + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + protected void buildReplacementMap(List propertyMappingClasses, CombinedIndexBuildItem index, + PanacheMongoRecorder recorder) { + Map> replacementMap = new ConcurrentHashMap<>(); + for (PropertyMappingClassBuildStep classToMap : propertyMappingClasses) { + DotName dotName = createSimple(classToMap.getClassName()); + ClassInfo classInfo = index.getIndex().getClassByName(dotName); + if (classInfo != null) { + // only compute field replacement for types inside the index + Map classReplacementMap = replacementMap.computeIfAbsent(classToMap.getClassName(), + className -> computeReplacement(classInfo)); + if (classToMap.getAliasClassName() != null) { + // also register the replacement map for the projection classes + replacementMap.put(classToMap.getAliasClassName(), classReplacementMap); + } + } + } + + recorder.setReplacementCache(replacementMap); + } + + private Map computeReplacement(ClassInfo classInfo) { + Map replacementMap = new HashMap<>(); + for (FieldInfo field : classInfo.fields()) { + AnnotationInstance bsonProperty = field.annotation(BSON_PROPERTY); + if (bsonProperty != null) { + replacementMap.put(field.name(), bsonProperty.value().asString()); + } + } + for (MethodInfo method : classInfo.methods()) { + if (method.name().startsWith("get")) { + // we try to replace also for getter + AnnotationInstance bsonProperty = method.annotation(BSON_PROPERTY); + if (bsonProperty != null) { + String fieldName = JavaBeanUtil.decapitalize(method.name().substring(3)); + replacementMap.put(fieldName, bsonProperty.value().asString()); + } + } + } + return replacementMap.isEmpty() ? Collections.emptyMap() : replacementMap; + } + + protected abstract PanacheEntityEnhancer createEntityEnhancer(CombinedIndexBuildItem index, + List methodCustomizers); + + protected abstract PanacheEntityEnhancer createReactiveEntityEnhancer(CombinedIndexBuildItem index, + List methodCustomizers); + + protected abstract PanacheRepositoryEnhancer createReactiveRepositoryEnhancer(CombinedIndexBuildItem index); + + protected abstract PanacheRepositoryEnhancer createRepositoryEnhancer(CombinedIndexBuildItem index); + + private void extractMappings(Map classPropertyMapping, ClassInfo target, CombinedIndexBuildItem index) { + for (FieldInfo fieldInfo : target.fields()) { + if (fieldInfo.hasAnnotation(BSON_PROPERTY)) { + AnnotationInstance bsonProperty = fieldInfo.annotation(BSON_PROPERTY); + classPropertyMapping.put(fieldInfo.name(), bsonProperty.value().asString()); + } + } + for (MethodInfo methodInfo : target.methods()) { + if (methodInfo.hasAnnotation(BSON_PROPERTY)) { + AnnotationInstance bsonProperty = methodInfo.annotation(BSON_PROPERTY); + classPropertyMapping.put(methodInfo.name(), bsonProperty.value().asString()); + } + } + + // climb up the hierarchy of types + if (!target.superClassType().name().equals(JandexUtil.DOTNAME_OBJECT)) { + Type superType = target.superClassType(); + ClassInfo superClass = index.getIndex().getClassByName(superType.name()); + extractMappings(classPropertyMapping, superClass, index); + } + } + + @BuildStep + protected PanacheEntityClassesBuildItem findEntityClasses(List entityClasses) { + if (!entityClasses.isEmpty()) { + Set ret = new HashSet<>(); + for (PanacheMongoEntityClassBuildItem entityClass : entityClasses) { + ret.add(entityClass.get().name().toString()); + } + return new PanacheEntityClassesBuildItem(ret); + } + return null; + } + + protected abstract TypeBundle getImperativeTypeBundle(); + + protected abstract TypeBundle getReactiveTypeBundle(); + + @BuildStep + protected void handleProjectionFor(CombinedIndexBuildItem index, + BuildProducer propertyMappingClass, + BuildProducer transformers) { + // manage @BsonProperty for the @ProjectionFor annotation + Map> propertyMapping = new HashMap<>(); + for (AnnotationInstance annotationInstance : index.getIndex().getAnnotations(PROJECTION_FOR)) { + Type targetClass = annotationInstance.value().asClass(); + ClassInfo target = index.getIndex().getClassByName(targetClass.name()); + Map classPropertyMapping = new HashMap<>(); + extractMappings(classPropertyMapping, target, index); + propertyMapping.put(targetClass.name(), classPropertyMapping); + } + for (AnnotationInstance annotationInstance : index.getIndex().getAnnotations(PROJECTION_FOR)) { + Type targetClass = annotationInstance.value().asClass(); + Map targetPropertyMapping = propertyMapping.get(targetClass.name()); + if (targetPropertyMapping != null && !targetPropertyMapping.isEmpty()) { + ClassInfo info = annotationInstance.target().asClass(); + ProjectionForEnhancer fieldEnhancer = new ProjectionForEnhancer(targetPropertyMapping); + transformers.produce(new BytecodeTransformerBuildItem(info.name().toString(), fieldEnhancer)); + } + + // Register for building the property mapping cache + propertyMappingClass + .produce(new PropertyMappingClassBuildStep(targetClass.name().toString(), + annotationInstance.target().asClass().name().toString())); + } + } + + @BuildStep + public void mongoClientNames(ApplicationArchivesBuildItem applicationArchivesBuildItem, + BuildProducer mongoClientName) { + Set values = new HashSet<>(); + IndexView indexView = applicationArchivesBuildItem.getRootArchive().getIndex(); + Collection instances = indexView.getAnnotations(MONGO_ENTITY); + for (AnnotationInstance annotation : instances) { + AnnotationValue clientName = annotation.value("clientName"); + if ((clientName != null) && !clientName.asString().isEmpty()) { + values.add(clientName.asString()); + } + } + for (String value : values) { + // we don't want the qualifier @MongoClientName qualifier added + // as these clients will only be looked up programmatically via name + // see MongoOperations#mongoClient + mongoClientName.produce(new MongoClientNameBuildItem(value, false)); + } + } + + protected void processEntities(CombinedIndexBuildItem index, + BuildProducer transformers, BuildProducer reflectiveClass, + BuildProducer propertyMappingClass, + PanacheEntityEnhancer entityEnhancer, TypeBundle typeBundle) { + + 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(typeBundle.entityBase().dotName())) { + if (classInfo.name().equals(typeBundle.entity().dotName())) { + continue; + } + if (modelClasses.add(classInfo.name().toString())) + entityEnhancer.collectFields(classInfo); + } + for (ClassInfo classInfo : index.getIndex().getAllKnownSubclasses(typeBundle.entity().dotName())) { + if (modelClasses.add(classInfo.name().toString())) + entityEnhancer.collectFields(classInfo); + } + + // iterate over all the entity classes + for (String modelClass : modelClasses) { + transformers.produce(new BytecodeTransformerBuildItem(modelClass, entityEnhancer)); + + //register for reflection entity classes + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, modelClass)); + + // Register for building the property mapping cache + propertyMappingClass.produce(new PropertyMappingClassBuildStep(modelClass)); + } + } + + protected void processRepositories(CombinedIndexBuildItem index, + BuildProducer transformers, + BuildProducer reflectiveClass, + BuildProducer propertyMappingClass, + PanacheRepositoryEnhancer repositoryEnhancer, TypeBundle typeBundle) { + + Set daoClasses = new HashSet<>(); + Set daoTypeParameters = new HashSet<>(); + for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(typeBundle.repositoryBase().dotName())) { + // Skip PanacheMongoRepository and abstract repositories + if (classInfo.name().equals(typeBundle.repository().dotName()) || repositoryEnhancer.skipRepository(classInfo)) { + continue; + } + daoClasses.add(classInfo.name().toString()); + daoTypeParameters.addAll( + resolveTypeParameters(classInfo.name(), typeBundle.repositoryBase().dotName(), index.getIndex())); + } + for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(typeBundle.repository().dotName())) { + if (repositoryEnhancer.skipRepository(classInfo)) { + continue; + } + daoClasses.add(classInfo.name().toString()); + daoTypeParameters.addAll( + resolveTypeParameters(classInfo.name(), typeBundle.repositoryBase().dotName(), index.getIndex())); + } + for (String daoClass : daoClasses) { + transformers.produce(new BytecodeTransformerBuildItem(daoClass, repositoryEnhancer)); + } + + for (Type parameterType : daoTypeParameters) { + // Register for reflection the type parameters of the repository: this should be the entity class and the ID class + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, parameterType.name().toString())); + + // Register for building the property mapping cache + propertyMappingClass.produce(new PropertyMappingClassBuildStep(parameterType.name().toString())); + } + } + + protected void processTypes(CombinedIndexBuildItem index, + BuildProducer transformers, + BuildProducer reflectiveClass, + BuildProducer propertyMappingClass, + TypeBundle typeBundle, PanacheRepositoryEnhancer repositoryEnhancer, + PanacheEntityEnhancer entityEnhancer) { + + processRepositories(index, transformers, reflectiveClass, propertyMappingClass, + repositoryEnhancer, typeBundle); + processEntities(index, transformers, reflectiveClass, propertyMappingClass, + entityEnhancer, typeBundle); + } + + @BuildStep + protected ReflectiveHierarchyBuildItem registerForReflection(CombinedIndexBuildItem index) { + Indexer indexer = new Indexer(); + Set additionalIndex = new HashSet<>(); + IndexingUtil.indexClass(ObjectId.class.getName(), indexer, index.getIndex(), additionalIndex, + BasePanacheMongoResourceProcessor.class.getClassLoader()); + CompositeIndex compositeIndex = CompositeIndex.create(index.getIndex(), indexer.complete()); + Type type = Type.create(OBJECT_ID, Type.Kind.CLASS); + return new ReflectiveHierarchyBuildItem(type, compositeIndex); + } + + @BuildStep + protected void registerJacksonSerDeser(BuildProducer customSerDeser) { + customSerDeser.produce( + new JacksonModuleBuildItem.Builder("ObjectIdModule") + .add(ObjectIdSerializer.class.getName(), + ObjectIdDeserializer.class.getName(), + ObjectId.class.getName()) + .build()); + } + + @BuildStep + protected void registerJsonbSerDeser(BuildProducer jsonbSerializers, + BuildProducer jsonbDeserializers) { + jsonbSerializers + .produce(new JsonbSerializerBuildItem(io.quarkus.mongodb.panache.jsonb.ObjectIdSerializer.class.getName())); + jsonbDeserializers + .produce(new JsonbDeserializerBuildItem(io.quarkus.mongodb.panache.jsonb.ObjectIdDeserializer.class.getName())); + } + + @BuildStep + public void unremovableClients(BuildProducer unremovable) { + unremovable.produce(new MongoUnremovableClientsBuildItem()); + } + + @BuildStep + protected ValidationPhaseBuildItem.ValidationErrorBuildItem validate(ValidationPhaseBuildItem validationPhase, + CombinedIndexBuildItem index) throws BuildException { + // we verify that no ID fields are defined (via @BsonId) when extending PanacheMongoEntity or ReactivePanacheMongoEntity + for (AnnotationInstance annotationInstance : index.getIndex().getAnnotations(BSON_ID)) { + ClassInfo info = JandexUtil.getEnclosingClass(annotationInstance); + if (JandexUtil.isSubclassOf(index.getIndex(), info, + getImperativeTypeBundle().entity().dotName())) { + BuildException be = new BuildException("You provide a MongoDB identifier via @BsonId inside '" + info.name() + + "' but one is already provided by PanacheMongoEntity, " + + "your class should extend PanacheMongoEntityBase instead, or use the id provided by PanacheMongoEntity", + Collections.emptyList()); + return new ValidationPhaseBuildItem.ValidationErrorBuildItem(be); + } else if (JandexUtil.isSubclassOf(index.getIndex(), info, + getReactiveTypeBundle().entity().dotName())) { + BuildException be = new BuildException("You provide a MongoDB identifier via @BsonId inside '" + info.name() + + "' but one is already provided by ReactivePanacheMongoEntity, " + + "your class should extend ReactivePanacheMongoEntityBase instead, or use the id provided by ReactivePanacheMongoEntity", + Collections.emptyList()); + return new ValidationPhaseBuildItem.ValidationErrorBuildItem(be); + } + } + return null; + } +} diff --git a/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ByteCodeType.java b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ByteCodeType.java new file mode 100644 index 0000000000000..498bd126b2edd --- /dev/null +++ b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ByteCodeType.java @@ -0,0 +1,57 @@ +package io.quarkus.mongodb.panache.deployment; + +import static org.jboss.jandex.DotName.createSimple; +import static org.objectweb.asm.Type.getType; + +import java.util.StringJoiner; + +import org.jboss.jandex.DotName; +import org.objectweb.asm.Type; + +import io.quarkus.deployment.util.AsmUtil; + +public class ByteCodeType { + private final Type type; + + public ByteCodeType(Type type) { + this.type = type; + } + + public ByteCodeType(Class type) { + this.type = getType(type); + } + + public ByteCodeType(org.jboss.jandex.Type type) { + String typeDescriptor = type.kind() == org.jboss.jandex.Type.Kind.PRIMITIVE + ? type.toString() + : "L" + type.name().toString().replace('.', '/') + ";"; + this.type = getType(typeDescriptor); + } + + public String descriptor() { + return type.getDescriptor(); + } + + public DotName dotName() { + return createSimple(type.getClassName()); + } + + public String internalName() { + return type.getInternalName(); + } + + @Override + public String toString() { + return new StringJoiner(", ", ByteCodeType.class.getSimpleName() + "[", "]") + .add(type.toString()) + .toString(); + } + + public Type type() { + return this.type; + } + + public ByteCodeType unbox() { + return new ByteCodeType(AsmUtil.WRAPPER_TO_PRIMITIVE.getOrDefault(type, type)); + } +} diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityClassBuildItem.java b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityClassBuildItem.java similarity index 100% rename from extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityClassBuildItem.java rename to extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityClassBuildItem.java diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ProjectionForEnhancer.java b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ProjectionForEnhancer.java similarity index 100% rename from extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ProjectionForEnhancer.java rename to extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ProjectionForEnhancer.java diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PropertyMappingClassBuildStep.java b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PropertyMappingClassBuildStep.java similarity index 88% rename from extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PropertyMappingClassBuildStep.java rename to extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PropertyMappingClassBuildStep.java index dd75677c27f79..a3a178f786c80 100644 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PropertyMappingClassBuildStep.java +++ b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PropertyMappingClassBuildStep.java @@ -2,7 +2,7 @@ import io.quarkus.builder.item.MultiBuildItem; -final class PropertyMappingClassBuildStep extends MultiBuildItem { +public final class PropertyMappingClassBuildStep extends MultiBuildItem { private String className; private String aliasClassName; diff --git a/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/TypeBundle.java b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/TypeBundle.java new file mode 100644 index 0000000000000..a0d5f99671896 --- /dev/null +++ b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/TypeBundle.java @@ -0,0 +1,23 @@ +package io.quarkus.mongodb.panache.deployment; + +public interface TypeBundle { + ByteCodeType entity(); + + ByteCodeType entityBase(); + + ByteCodeType entityBaseCompanion(); + + ByteCodeType entityCompanion(); + + ByteCodeType entityCompanionBase(); + + ByteCodeType operations(); + + ByteCodeType queryType(); + + ByteCodeType repository(); + + ByteCodeType repositoryBase(); + + ByteCodeType updateType(); +} diff --git a/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/visitors/PanacheMongoEntityClassVisitor.java b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/visitors/PanacheMongoEntityClassVisitor.java new file mode 100644 index 0000000000000..a9448f118af45 --- /dev/null +++ b/extensions/panache/mongodb-panache-common/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/visitors/PanacheMongoEntityClassVisitor.java @@ -0,0 +1,128 @@ +package io.quarkus.mongodb.panache.deployment.visitors; + +import static io.quarkus.deployment.util.AsmUtil.getDescriptor; +import static io.quarkus.mongodb.panache.deployment.BasePanacheMongoResourceProcessor.OBJECT_SIGNATURE; +import static java.util.Arrays.asList; +import static org.objectweb.asm.Opcodes.CHECKCAST; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; + +import java.util.List; +import java.util.StringJoiner; +import java.util.function.Function; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import io.quarkus.deployment.util.AsmUtil; +import io.quarkus.mongodb.panache.deployment.ByteCodeType; +import io.quarkus.mongodb.panache.deployment.TypeBundle; +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; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; +import io.quarkus.panache.common.deployment.visitors.PanacheEntityClassVisitor; + +public class PanacheMongoEntityClassVisitor extends PanacheEntityClassVisitor { + private static final ByteCodeType CLASS = new ByteCodeType(Class.class); + + private final TypeBundle typeBundle; + + public PanacheMongoEntityClassVisitor(String className, + ClassVisitor outputClassVisitor, + MetamodelInfo> modelInfo, + ClassInfo panacheEntityBaseClassInfo, + ClassInfo entityInfo, + List methodCustomizers, + TypeBundle typeBundle) { + super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo, methodCustomizers); + this.typeBundle = typeBundle; + } + + @Override + protected void generateMethod(MethodInfo method, AnnotationValue targetReturnTypeErased) { + String descriptor = AsmUtil.getDescriptor(method, name -> null); + String signature = AsmUtil.getSignature(method, name -> null); + List parameters = method.parameters(); + + MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, + method.name(), + descriptor, + signature, + null); + for (int i = 0; i < parameters.size(); i++) { + mv.visitParameter(method.parameterName(i), 0 /* modifiers */); + } + mv.visitCode(); + for (PanacheMethodCustomizer customizer : methodCustomizers) { + customizer.customize(thisClass, method, mv); + } + mv.visitFieldInsn(Opcodes.GETSTATIC, thisClass.getInternalName(), "operations", + typeBundle.operations().descriptor()); + injectModel(mv); + for (int i = 0; i < parameters.size(); i++) { + mv.visitIntInsn(Opcodes.ALOAD, i); + } + invokeOperation(mv, method, method.parameters()); + mv.visitMaxs(0, 0); + mv.visitEnd(); + + } + + private void invokeOperation(MethodVisitor mv, MethodInfo method, List parameters) { + String operationDescriptor; + Function argMapper = type -> null; + + AnnotationInstance bridge = method.annotation(PanacheEntityEnhancer.DOTNAME_GENERATE_BRIDGE); + AnnotationValue targetReturnTypeErased = bridge.value("targetReturnTypeErased"); + boolean erased = targetReturnTypeErased != null && targetReturnTypeErased.asBoolean(); + + StringJoiner joiner = new StringJoiner("", "(", ")"); + joiner.add(CLASS.descriptor()); + for (Type parameter : parameters) { + joiner.add(getDescriptor(parameter, argMapper)); + } + + List names = asList(typeBundle.queryType().dotName().toString(), + typeBundle.updateType().dotName().toString()); + operationDescriptor = joiner + + (erased || names.contains(method.returnType().name().toString()) + ? OBJECT_SIGNATURE + : getDescriptor(method.returnType(), argMapper)); + + mv.visitMethodInsn(INVOKEVIRTUAL, typeBundle.operations().internalName(), method.name(), + operationDescriptor, false); + if (method.returnType().kind() != Type.Kind.PRIMITIVE) { + Type type = method.returnType(); + String cast; + if (erased) { + cast = thisClass.getInternalName(); + } else { + cast = type.name().toString().replace('.', '/'); + } + mv.visitTypeInsn(CHECKCAST, cast); + } + mv.visitInsn(AsmUtil.getReturnInstruction(method.returnType())); + } + + @Override + protected String getPanacheOperationsInternalName() { + return typeBundle.operations().internalName(); + } + + @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); + } +} diff --git a/extensions/panache/mongodb-panache-common/pom.xml b/extensions/panache/mongodb-panache-common/pom.xml new file mode 100644 index 0000000000000..9510146247760 --- /dev/null +++ b/extensions/panache/mongodb-panache-common/pom.xml @@ -0,0 +1,20 @@ + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../../build-parent/pom.xml + + 4.0.0 + + quarkus-mongodb-panache-common-parent + Quarkus - MongoDB with Panache - Common + pom + + runtime + deployment + + \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-common/runtime/pom.xml b/extensions/panache/mongodb-panache-common/runtime/pom.xml new file mode 100644 index 0000000000000..b27a973cba4d0 --- /dev/null +++ b/extensions/panache/mongodb-panache-common/runtime/pom.xml @@ -0,0 +1,88 @@ + + + + quarkus-mongodb-panache-common-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-mongodb-panache-common + Quarkus - MongoDB with Panache - Common Runtime + + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-panache-common + + + io.quarkus + quarkus-mongodb-client + + + io.quarkus + quarkus-panacheql + + + + + io.quarkus + quarkus-jsonb + true + + + + + io.quarkus + quarkus-jackson + true + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + + maven-compiler-plugin + + -proc:none + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + + org.jboss.jandex + jandex-maven-plugin + + + make-index + + jandex + + + + + + + + + diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/MongoEntity.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/MongoEntity.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/MongoEntity.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/MongoEntity.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRecorder.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRecorder.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRecorder.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRecorder.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheUpdate.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheUpdate.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheUpdate.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheUpdate.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/ProjectionFor.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/ProjectionFor.java similarity index 92% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/ProjectionFor.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/ProjectionFor.java index 6f74d7aab4a48..260267b716cb5 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/ProjectionFor.java +++ b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/ProjectionFor.java @@ -10,5 +10,5 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ProjectionFor { - public Class value(); + Class value(); } diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/CommonQueryBinder.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/MongoParserVisitor.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/MongoParserVisitor.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/MongoParserVisitor.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/MongoParserVisitor.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/NativeQueryBinder.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/NativeQueryBinder.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/NativeQueryBinder.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/NativeQueryBinder.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/PanacheQlQueryBinder.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/PanacheQlQueryBinder.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/binder/PanacheQlQueryBinder.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/binder/PanacheQlQueryBinder.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/jackson/ObjectIdDeserializer.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/jackson/ObjectIdDeserializer.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/jackson/ObjectIdDeserializer.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/jackson/ObjectIdDeserializer.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/jackson/ObjectIdSerializer.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/jackson/ObjectIdSerializer.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/jackson/ObjectIdSerializer.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/jackson/ObjectIdSerializer.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/jsonb/ObjectIdDeserializer.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/jsonb/ObjectIdDeserializer.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/jsonb/ObjectIdDeserializer.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/jsonb/ObjectIdDeserializer.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/jsonb/ObjectIdSerializer.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/jsonb/ObjectIdSerializer.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/jsonb/ObjectIdSerializer.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/jsonb/ObjectIdSerializer.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheUpdate.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheUpdate.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheUpdate.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/ReactivePanacheUpdate.java diff --git a/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/CommonReactivePanacheQueryImpl.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/CommonReactivePanacheQueryImpl.java new file mode 100644 index 0000000000000..b767f36126dfa --- /dev/null +++ b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/CommonReactivePanacheQueryImpl.java @@ -0,0 +1,238 @@ +package io.quarkus.mongodb.panache.reactive.runtime; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.bson.Document; +import org.bson.conversions.Bson; + +import com.mongodb.client.model.Collation; + +import io.quarkus.mongodb.FindOptions; +import io.quarkus.mongodb.panache.runtime.MongoPropertyUtil; +import io.quarkus.mongodb.reactive.ReactiveMongoCollection; +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Range; +import io.quarkus.panache.common.exception.PanacheQueryException; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +public class CommonReactivePanacheQueryImpl { + private ReactiveMongoCollection collection; + private Bson mongoQuery; + private Bson sort; + private Bson projections; + + private Page page; + private Uni count; + + private Range range; + + private Collation collation; + + public CommonReactivePanacheQueryImpl(ReactiveMongoCollection collection, Bson mongoQuery, Bson sort) { + this.collection = collection; + this.mongoQuery = mongoQuery; + this.sort = sort; + } + + private CommonReactivePanacheQueryImpl(CommonReactivePanacheQueryImpl previousQuery, Bson projections, Class type) { + this.collection = previousQuery.collection.withDocumentClass(type); + this.mongoQuery = previousQuery.mongoQuery; + this.sort = previousQuery.sort; + this.projections = projections; + this.page = previousQuery.page; + this.count = previousQuery.count; + this.range = previousQuery.range; + this.collation = previousQuery.collation; + } + + // Builder + + public CommonReactivePanacheQueryImpl project(Class type) { + // collect field names from public fields and getters + Set fieldNames = MongoPropertyUtil.collectFields(type); + + // create the projection document + Document projections = new Document(); + for (String fieldName : fieldNames) { + projections.append(fieldName, 1); + } + + return new CommonReactivePanacheQueryImpl(this, projections, type); + } + + @SuppressWarnings("unchecked") + public CommonReactivePanacheQueryImpl page(Page page) { + this.page = page; + this.range = null; // reset the range to be able to switch from range to page + return (CommonReactivePanacheQueryImpl) this; + } + + public CommonReactivePanacheQueryImpl page(int pageIndex, int pageSize) { + return page(Page.of(pageIndex, pageSize)); + } + + public CommonReactivePanacheQueryImpl nextPage() { + checkPagination(); + return page(page.next()); + } + + public CommonReactivePanacheQueryImpl previousPage() { + checkPagination(); + return page(page.previous()); + } + + public CommonReactivePanacheQueryImpl firstPage() { + checkPagination(); + return page(page.first()); + } + + public Uni> lastPage() { + checkPagination(); + Uni> map = pageCount().map(pageCount -> page(page.index(pageCount - 1))); + return map; + } + + public Uni hasNextPage() { + checkPagination(); + return pageCount().map(pageCount -> page.index < (pageCount - 1)); + } + + public boolean hasPreviousPage() { + checkPagination(); + return page.index > 0; + } + + public Uni pageCount() { + checkPagination(); + return count().map(count -> { + if (count == 0) + return 1; // a single page of zero results + return (int) Math.ceil((double) count / (double) page.size); + }); + } + + public Page page() { + checkPagination(); + return page; + } + + private void checkPagination() { + if (page == null) { + throw new UnsupportedOperationException( + "Cannot call a page related method, " + + "call page(Page) or page(int, int) to initiate pagination first"); + } + if (range != null) { + throw new UnsupportedOperationException("Cannot call a page related method in a ranged query, " + + "call page(Page) or page(int, int) to initiate pagination first"); + } + } + + public CommonReactivePanacheQueryImpl range(int startIndex, int lastIndex) { + this.range = Range.of(startIndex, lastIndex); + // reset the page to its default to be able to switch from page to range + this.page = null; + return (CommonReactivePanacheQueryImpl) this; + } + + public CommonReactivePanacheQueryImpl withCollation(Collation collation) { + this.collation = collation; + return (CommonReactivePanacheQueryImpl) this; + } + + // Results + + @SuppressWarnings("unchecked") + public Uni count() { + if (count == null) { + count = collection.countDocuments(mongoQuery); + } + return count; + } + + @SuppressWarnings("unchecked") + public Uni> list() { + Multi results = stream(); + return results.collectItems().asList(); + } + + @SuppressWarnings("unchecked") + public Multi stream() { + FindOptions options = buildOptions(); + return mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + } + + public Uni firstResult() { + Uni> optionalEntity = firstResultOptional(); + return optionalEntity.map(optional -> optional.orElse(null)); + } + + public Uni> firstResultOptional() { + FindOptions options = buildOptions(1); + Multi results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + return results.collectItems().first().map(o -> Optional.ofNullable(o)); + } + + @SuppressWarnings("unchecked") + public Uni singleResult() { + FindOptions options = buildOptions(2); + Multi results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + return results.collectItems().asList().map(list -> { + if (list.size() != 1) { + throw new PanacheQueryException("There should be only one result"); + } else { + return list.get(0); + } + }); + } + + public Uni> singleResultOptional() { + FindOptions options = buildOptions(2); + Multi results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + return results.collectItems().asList().map(list -> { + if (list.size() == 2) { + throw new PanacheQueryException("There should be no more than one result"); + } + return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); + }); + } + + private FindOptions buildOptions() { + FindOptions options = new FindOptions(); + options.sort(sort); + if (range != null) { + // range is 0 based, so we add 1 to the limit + options.skip(range.getStartIndex()).limit(range.getLastIndex() - range.getStartIndex() + 1); + } else if (page != null) { + options.skip(page.index * page.size).limit(page.size); + } + if (projections != null) { + options.projection(this.projections); + } + if (this.collation != null) { + options.collation(collation); + } + return options; + } + + private FindOptions buildOptions(int maxResults) { + FindOptions options = new FindOptions(); + options.sort(sort); + if (range != null) { + // range is 0 based, so we add 1 to the limit + options.skip(range.getStartIndex()); + } else if (page != null) { + options.skip(page.index * page.size); + } + if (projections != null) { + options.projection(this.projections); + } + if (this.collation != null) { + options.collation(collation); + } + return options.limit(maxResults); + } +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperations.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperations.java similarity index 67% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperations.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperations.java index b092143b53a49..38671bff4399f 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperations.java +++ b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactiveMongoOperations.java @@ -30,8 +30,6 @@ 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.panache.reactive.ReactivePanacheUpdate; import io.quarkus.mongodb.reactive.ReactiveMongoClient; import io.quarkus.mongodb.reactive.ReactiveMongoCollection; import io.quarkus.mongodb.reactive.ReactiveMongoDatabase; @@ -40,21 +38,27 @@ import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; -public class ReactiveMongoOperations { +@SuppressWarnings({ "rawtypes", "unchecked", "Convert2MethodRef" }) +public abstract class ReactiveMongoOperations { + public final String ID = "_id"; private static final Logger LOGGER = Logger.getLogger(ReactiveMongoOperations.class); - public static final String ID = "_id"; - private static final Map defaultDatabaseName = new ConcurrentHashMap<>(); - // - // Instance methods + protected abstract QueryType createQuery(ReactiveMongoCollection collection, Document query, Document sortDoc); + + protected abstract UpdateType createUpdate(ReactiveMongoCollection collection, Class entityClass, + Document docUpdate); + + protected abstract Uni list(QueryType query); - public static Uni persist(Object entity) { + protected abstract Multi stream(QueryType query); + + public Uni persist(Object entity) { ReactiveMongoCollection collection = mongoCollection(entity); return persist(collection, entity); } - public static Uni persist(Iterable entities) { + public 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<>(); @@ -72,7 +76,7 @@ public static Uni persist(Iterable entities) { }); } - public static Uni persist(Object firstEntity, Object... entities) { + public Uni persist(Object firstEntity, Object... entities) { ReactiveMongoCollection collection = mongoCollection(firstEntity); if (entities == null || entities.length == 0) { return persist(collection, firstEntity); @@ -84,7 +88,7 @@ public static Uni persist(Object firstEntity, Object... entities) { } } - public static Uni persist(Stream entities) { + public Uni persist(Stream entities) { return Uni.createFrom().deferred(() -> { List objects = entities.collect(Collectors.toList()); if (objects.size() > 0) { @@ -97,12 +101,12 @@ public static Uni persist(Stream entities) { }); } - public static Uni update(Object entity) { + public Uni update(Object entity) { ReactiveMongoCollection collection = mongoCollection(entity); return update(collection, entity); } - public static Uni update(Iterable entities) { + public 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<>(); @@ -120,7 +124,7 @@ public static Uni update(Iterable entities) { }); } - public static Uni update(Object firstEntity, Object... entities) { + public Uni update(Object firstEntity, Object... entities) { ReactiveMongoCollection collection = mongoCollection(firstEntity); if (entities == null || entities.length == 0) { return update(collection, firstEntity); @@ -132,7 +136,7 @@ public static Uni update(Object firstEntity, Object... entities) { } } - public static Uni update(Stream entities) { + public Uni update(Stream entities) { return Uni.createFrom().deferred(() -> { List objects = entities.collect(Collectors.toList()); if (objects.size() > 0) { @@ -145,12 +149,12 @@ public static Uni update(Stream entities) { }); } - public static Uni persistOrUpdate(Object entity) { + public Uni persistOrUpdate(Object entity) { ReactiveMongoCollection collection = mongoCollection(entity); return persistOrUpdate(collection, entity); } - public static Uni persistOrUpdate(Iterable entities) { + public Uni persistOrUpdate(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<>(); @@ -168,7 +172,7 @@ public static Uni persistOrUpdate(Iterable entities) { }); } - public static Uni persistOrUpdate(Object firstEntity, Object... entities) { + public Uni persistOrUpdate(Object firstEntity, Object... entities) { ReactiveMongoCollection collection = mongoCollection(firstEntity); if (entities == null || entities.length == 0) { return persistOrUpdate(collection, firstEntity); @@ -180,7 +184,7 @@ public static Uni persistOrUpdate(Object firstEntity, Object... entities) } } - public static Uni persistOrUpdate(Stream entities) { + public Uni persistOrUpdate(Stream entities) { return Uni.createFrom().deferred(() -> { List objects = entities.collect(Collectors.toList()); if (objects.size() > 0) { @@ -193,7 +197,7 @@ public static Uni persistOrUpdate(Stream entities) { }); } - public static Uni delete(Object entity) { + public Uni delete(Object entity) { ReactiveMongoCollection collection = mongoCollection(entity); BsonDocument document = getBsonDocument(collection, entity); BsonValue id = document.get(ID); @@ -201,7 +205,7 @@ public static Uni delete(Object entity) { return collection.deleteOne(query).onItem().ignore().andContinueWithNull(); } - public static ReactiveMongoCollection mongoCollection(Class entityClass) { + public ReactiveMongoCollection mongoCollection(Class entityClass) { MongoEntity mongoEntity = entityClass.getAnnotation(MongoEntity.class); ReactiveMongoDatabase database = mongoDatabase(mongoEntity); if (mongoEntity != null && !mongoEntity.collection().isEmpty()) { @@ -210,7 +214,7 @@ public static ReactiveMongoCollection mongoCollection(Class entityClass) { return database.getCollection(entityClass.getSimpleName(), entityClass); } - public static ReactiveMongoDatabase mongoDatabase(Class entityClass) { + public ReactiveMongoDatabase mongoDatabase(Class entityClass) { MongoEntity mongoEntity = entityClass.getAnnotation(MongoEntity.class); return mongoDatabase(mongoEntity); } @@ -218,19 +222,19 @@ public static ReactiveMongoDatabase mongoDatabase(Class entityClass) { // // Private stuff - public static Uni nullUni() { + public Uni nullUni() { return Uni.createFrom().item((Void) null); } - private static Uni persist(ReactiveMongoCollection collection, Object entity) { + private Uni persist(ReactiveMongoCollection collection, Object entity) { return collection.insertOne(entity).onItem().ignore().andContinueWithNull(); } - private static Uni persist(ReactiveMongoCollection collection, List entities) { + private Uni persist(ReactiveMongoCollection collection, List entities) { return collection.insertMany(entities).onItem().ignore().andContinueWithNull(); } - private static Uni update(ReactiveMongoCollection collection, Object entity) { + private Uni update(ReactiveMongoCollection collection, Object entity) { //we transform the entity as a document first BsonDocument document = getBsonDocument(collection, entity); @@ -240,12 +244,12 @@ private static Uni update(ReactiveMongoCollection collection, Object entit return collection.replaceOne(query, entity).onItem().ignore().andContinueWithNull(); } - private static Uni update(ReactiveMongoCollection collection, List entities) { + private Uni update(ReactiveMongoCollection collection, List entities) { List> unis = entities.stream().map(entity -> update(collection, entity)).collect(Collectors.toList()); return Uni.combine().all().unis(unis).combinedWith(u -> null); } - private static Uni persistOrUpdate(ReactiveMongoCollection collection, Object entity) { + private Uni persistOrUpdate(ReactiveMongoCollection collection, Object entity) { //we transform the entity as a document first BsonDocument document = getBsonDocument(collection, entity); @@ -262,7 +266,7 @@ private static Uni persistOrUpdate(ReactiveMongoCollection collection, Obj } } - private static Uni persistOrUpdate(ReactiveMongoCollection collection, List entities) { + private 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) { @@ -285,19 +289,19 @@ private static Uni persistOrUpdate(ReactiveMongoCollection collection, Lis return collection.bulkWrite(bulk).onItem().ignore().andContinueWithNull(); } - private static BsonDocument getBsonDocument(ReactiveMongoCollection collection, Object entity) { + private 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) { + private ReactiveMongoCollection mongoCollection(Object entity) { Class entityClass = entity.getClass(); return mongoCollection(entityClass); } - private static ReactiveMongoDatabase mongoDatabase(MongoEntity entity) { + private ReactiveMongoDatabase mongoDatabase(MongoEntity entity) { ReactiveMongoClient mongoClient = clientFromArc(entity, ReactiveMongoClient.class); if (entity != null && !entity.database().isEmpty()) { return mongoClient.getDatabase(entity.database()); @@ -306,7 +310,7 @@ private static ReactiveMongoDatabase mongoDatabase(MongoEntity entity) { return mongoClient.getDatabase(databaseName); } - private static String getDefaultDatabaseName(MongoEntity entity) { + private String getDefaultDatabaseName(MongoEntity entity) { return defaultDatabaseName.computeIfAbsent(beanName(entity), new Function() { @Override public String apply(String beanName) { @@ -318,35 +322,35 @@ public String apply(String beanName) { // // Queries - public static Uni findById(Class entityClass, Object id) { + public Uni findById(Class entityClass, Object id) { Uni optionalEntity = findByIdOptional(entityClass, id); return optionalEntity.onItem().transform(optional -> optional.orElse(null)); } - public static Uni findByIdOptional(Class entityClass, Object id) { + public Uni findByIdOptional(Class entityClass, Object id) { ReactiveMongoCollection collection = mongoCollection(entityClass); return collection.find(new Document(ID, id)).collectItems().first() .onItem().transform(Optional::ofNullable); } - public static ReactivePanacheQuery find(Class entityClass, String query, Object... params) { + public QueryType 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) { + public QueryType find(Class entityClass, String query, Sort sort, Object... params) { String bindQuery = bindFilter(entityClass, query, params); - BsonDocument docQuery = BsonDocument.parse(bindQuery); + Document docQuery = Document.parse(bindQuery); Document docSort = sortToDocument(sort); ReactiveMongoCollection collection = mongoCollection(entityClass); - return new ReactivePanacheQueryImpl(collection, docQuery, docSort); + return createQuery(collection, docQuery, docSort); } /** * We should have a query like {'firstname': ?1, 'lastname': ?2} for native one * and like firstname = ?1 for PanacheQL one. */ - static String bindFilter(Class clazz, String query, Object[] params) { + public String bindFilter(Class clazz, String query, Object[] params) { String bindQuery = bindQuery(clazz, query, params); LOGGER.debug(bindQuery); return bindQuery; @@ -356,7 +360,7 @@ static String bindFilter(Class clazz, String query, Object[] params) { * 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 bindFilter(Class clazz, String query, Map params) { + public String bindFilter(Class clazz, String query, Map params) { String bindQuery = bindQuery(clazz, query, params); LOGGER.debug(bindQuery); return bindQuery; @@ -367,7 +371,7 @@ static String bindFilter(Class clazz, String query, Map param * and like firstname = ?1 and lastname = ?2 for PanacheQL one. * As update document needs a $set operator we add it if needed. */ - static String bindUpdate(Class clazz, String query, Object[] params) { + String bindUpdate(Class clazz, String query, Object[] params) { String bindUpdate = bindQuery(clazz, query, params); if (!bindUpdate.contains("$set")) { bindUpdate = "{'$set':" + bindUpdate + "}"; @@ -381,7 +385,7 @@ static String bindUpdate(Class clazz, String query, Object[] params) { * and like firstname = :firstname and lastname = :lastname for PanacheQL one. * As update document needs a $set operator we add it if needed. */ - static String bindUpdate(Class clazz, String query, Map params) { + String bindUpdate(Class clazz, String query, Map params) { String bindUpdate = bindQuery(clazz, query, params); if (!bindUpdate.contains("$set")) { bindUpdate = "{'$set':" + bindUpdate + "}"; @@ -390,7 +394,7 @@ static String bindUpdate(Class clazz, String query, Map param return bindUpdate; } - static String bindQuery(Class clazz, String query, Object[] params) { + String bindQuery(Class clazz, String query, Object[] params) { String bindQuery = null; //determine the type of the query @@ -405,7 +409,7 @@ static String bindQuery(Class clazz, String query, Object[] params) { return bindQuery; } - static String bindQuery(Class clazz, String query, Map params) { + String bindQuery(Class clazz, String query, Map params) { String bindQuery = null; //determine the type of the query @@ -420,125 +424,125 @@ static String bindQuery(Class clazz, String query, Map params return bindQuery; } - public static ReactivePanacheQuery find(Class entityClass, String query, Map params) { + public QueryType find(Class entityClass, String query, Map params) { return find(entityClass, query, null, params); } @SuppressWarnings("rawtypes") - public static ReactivePanacheQuery find(Class entityClass, String query, Sort sort, Map params) { + public QueryType find(Class entityClass, String query, Sort sort, Map params) { String bindQuery = bindFilter(entityClass, query, params); - BsonDocument docQuery = BsonDocument.parse(bindQuery); + Document docQuery = Document.parse(bindQuery); Document docSort = sortToDocument(sort); ReactiveMongoCollection collection = mongoCollection(entityClass); - return new ReactivePanacheQueryImpl(collection, docQuery, docSort); + return createQuery(collection, docQuery, docSort); } - public static ReactivePanacheQuery find(Class entityClass, String query, Parameters params) { + public QueryType 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) { + public QueryType find(Class entityClass, String query, Sort sort, Parameters params) { return find(entityClass, query, sort, params.map()); } @SuppressWarnings("rawtypes") - public static ReactivePanacheQuery find(Class entityClass, Document query, Sort sort) { + public QueryType find(Class entityClass, Document query, Sort sort) { ReactiveMongoCollection collection = mongoCollection(entityClass); Document sortDoc = sortToDocument(sort); - return new ReactivePanacheQueryImpl(collection, query, sortDoc); + return createQuery(collection, query, sortDoc); } - public static ReactivePanacheQuery find(Class entityClass, Document query, Document sort) { + public QueryType find(Class entityClass, Document query, Document sort) { ReactiveMongoCollection collection = mongoCollection(entityClass); - return new ReactivePanacheQueryImpl(collection, query, sort); + return createQuery(collection, query, sort); } - public static ReactivePanacheQuery find(Class entityClass, Document query) { + public QueryType 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 Uni> list(Class entityClass, String query, Object... params) { + return (Uni) list(find(entityClass, query, params)); } - public static Uni> list(Class entityClass, String query, Sort sort, Object... params) { - return (Uni) find(entityClass, query, sort, params).list(); + public Uni> list(Class entityClass, String query, Sort sort, Object... params) { + return (Uni) list(find(entityClass, query, sort, params)); } - public static Uni> list(Class entityClass, String query, Map params) { - return (Uni) find(entityClass, query, params).list(); + public Uni> list(Class entityClass, String query, Map params) { + return (Uni) list(find(entityClass, query, params)); } - public static Uni> list(Class entityClass, String query, Sort sort, Map params) { - return (Uni) find(entityClass, query, sort, params).list(); + public Uni> list(Class entityClass, String query, Sort sort, Map params) { + return (Uni) list(find(entityClass, query, sort, params)); } - public static Uni> list(Class entityClass, String query, Parameters params) { - return (Uni) find(entityClass, query, params).list(); + public Uni> list(Class entityClass, String query, Parameters params) { + return (Uni) list(find(entityClass, query, params)); } - public static Uni> list(Class entityClass, String query, Sort sort, Parameters params) { - return (Uni) find(entityClass, query, sort, params).list(); + public Uni> list(Class entityClass, String query, Sort sort, Parameters params) { + return (Uni) list(find(entityClass, query, sort, params)); } //specific Mongo query - public static Uni> list(Class entityClass, Document query) { - return (Uni) find(entityClass, query).list(); + public Uni> list(Class entityClass, Document query) { + return (Uni) list(find(entityClass, query)); } //specific Mongo query - public static Uni> list(Class entityClass, Document query, Document sort) { - return (Uni) find(entityClass, query, sort).list(); + public Uni> list(Class entityClass, Document query, Document sort) { + return (Uni) list(find(entityClass, query, sort)); } - public static Multi stream(Class entityClass, String query, Object... params) { - return find(entityClass, query, params).stream(); + public Multi stream(Class entityClass, String query, Object... params) { + return stream(find(entityClass, query, params)); } - public static Multi stream(Class entityClass, String query, Sort sort, Object... params) { - return find(entityClass, query, sort, params).stream(); + public Multi stream(Class entityClass, String query, Sort sort, Object... params) { + return stream(find(entityClass, query, sort, params)); } - public static Multi stream(Class entityClass, String query, Map params) { - return find(entityClass, query, params).stream(); + public Multi stream(Class entityClass, String query, Map params) { + return stream(find(entityClass, query, params)); } - public static Multi stream(Class entityClass, String query, Sort sort, Map params) { - return find(entityClass, query, sort, params).stream(); + public Multi stream(Class entityClass, String query, Sort sort, Map params) { + return stream(find(entityClass, query, sort, params)); } - public static Multi stream(Class entityClass, String query, Parameters params) { - return find(entityClass, query, params).stream(); + public Multi stream(Class entityClass, String query, Parameters params) { + return stream(find(entityClass, query, params)); } - public static Multi stream(Class entityClass, String query, Sort sort, Parameters params) { - return find(entityClass, query, sort, params).stream(); + public Multi stream(Class entityClass, String query, Sort sort, Parameters params) { + return stream(find(entityClass, query, sort, params)); } //specific Mongo query - public static Multi stream(Class entityClass, Document query) { - return find(entityClass, query).stream(); + public Multi stream(Class entityClass, Document query) { + return stream(find(entityClass, query)); } //specific Mongo query - public static Multi stream(Class entityClass, Document query, Document sort) { - return find(entityClass, query, sort).stream(); + public Multi stream(Class entityClass, Document query, Document sort) { + return stream(find(entityClass, query, sort)); } @SuppressWarnings("rawtypes") - public static ReactivePanacheQuery findAll(Class entityClass) { + public QueryType findAll(Class entityClass) { ReactiveMongoCollection collection = mongoCollection(entityClass); - return new ReactivePanacheQueryImpl(collection, null, null); + return createQuery(collection, null, null); } @SuppressWarnings("rawtypes") - public static ReactivePanacheQuery findAll(Class entityClass, Sort sort) { + public QueryType findAll(Class entityClass, Sort sort) { ReactiveMongoCollection collection = mongoCollection(entityClass); Document sortDoc = sortToDocument(sort); - return new ReactivePanacheQueryImpl(collection, null, sortDoc); + return createQuery(collection, null, sortDoc); } - private static Document sortToDocument(Sort sort) { + private Document sortToDocument(Sort sort) { if (sort == null) { return null; } @@ -550,115 +554,114 @@ private static Document sortToDocument(Sort sort) { return sortDoc; } - public static Uni> listAll(Class entityClass) { - return (Uni) findAll(entityClass).list(); + public Uni> listAll(Class entityClass) { + return (Uni) list(findAll(entityClass)); } - public static Uni> listAll(Class entityClass, Sort sort) { - return (Uni) findAll(entityClass, sort).list(); + public Uni> listAll(Class entityClass, Sort sort) { + return (Uni) list(findAll(entityClass, sort)); } - public static Multi streamAll(Class entityClass) { - return findAll(entityClass).stream(); + public Multi streamAll(Class entityClass) { + return stream(findAll(entityClass)); } - public static Multi streamAll(Class entityClass, Sort sort) { - return findAll(entityClass, sort).stream(); + public Multi streamAll(Class entityClass, Sort sort) { + return stream(findAll(entityClass, sort)); } - public static Uni count(Class entityClass) { + public Uni count(Class entityClass) { ReactiveMongoCollection collection = mongoCollection(entityClass); return collection.countDocuments(); } - public static Uni count(Class entityClass, String query, Object... params) { + public Uni count(Class entityClass, String query, Object... params) { String bindQuery = bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); ReactiveMongoCollection collection = mongoCollection(entityClass); return collection.countDocuments(docQuery); } - public static Uni count(Class entityClass, String query, Map params) { + public Uni count(Class entityClass, String query, Map params) { String bindQuery = bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); ReactiveMongoCollection collection = mongoCollection(entityClass); return collection.countDocuments(docQuery); } - public static Uni count(Class entityClass, String query, Parameters params) { + public 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) { + public Uni count(Class entityClass, Document query) { ReactiveMongoCollection collection = mongoCollection(entityClass); return collection.countDocuments(query); } - public static Uni deleteAll(Class entityClass) { + public Uni deleteAll(Class entityClass) { ReactiveMongoCollection collection = mongoCollection(entityClass); return collection.deleteMany(new Document()).map(deleteResult -> deleteResult.getDeletedCount()); } - public static Uni deleteById(Class entityClass, Object id) { + public Uni deleteById(Class entityClass, Object id) { ReactiveMongoCollection collection = mongoCollection(entityClass); Document query = new Document().append(ID, id); return collection.deleteOne(query).map(results -> results.getDeletedCount() == 1); } - public static Uni delete(Class entityClass, String query, Object... params) { + public Uni delete(Class entityClass, String query, Object... params) { String bindQuery = bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); ReactiveMongoCollection collection = mongoCollection(entityClass); return collection.deleteMany(docQuery).map(deleteResult -> deleteResult.getDeletedCount()); } - public static Uni delete(Class entityClass, String query, Map params) { + public Uni delete(Class entityClass, String query, Map params) { String bindQuery = bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); ReactiveMongoCollection collection = mongoCollection(entityClass); return collection.deleteMany(docQuery).map(deleteResult -> deleteResult.getDeletedCount()); } - public static Uni delete(Class entityClass, String query, Parameters params) { + public 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) { + public Uni delete(Class entityClass, Document query) { ReactiveMongoCollection collection = mongoCollection(entityClass); return collection.deleteMany(query).map(deleteResult -> deleteResult.getDeletedCount()); } - public static ReactivePanacheUpdate update(Class entityClass, String update, Map params) { + public UpdateType update(Class entityClass, String update, Map params) { return executeUpdate(entityClass, update, params); } - public static ReactivePanacheUpdate update(Class entityClass, String update, Parameters params) { + public UpdateType update(Class entityClass, String update, Parameters params) { return update(entityClass, update, params.map()); } - public static ReactivePanacheUpdate update(Class entityClass, String update, Object... params) { + public UpdateType update(Class entityClass, String update, Object... params) { return executeUpdate(entityClass, update, params); } - private static ReactivePanacheUpdate executeUpdate(Class entityClass, String update, Object... params) { + private UpdateType executeUpdate(Class entityClass, String update, Object... params) { String bindUpdate = bindUpdate(entityClass, update, params); - BsonDocument docUpdate = BsonDocument.parse(bindUpdate); + Document docUpdate = Document.parse(bindUpdate); ReactiveMongoCollection collection = mongoCollection(entityClass); - return new ReactivePanacheUpdateImpl(entityClass, docUpdate, collection); + return createUpdate(collection, entityClass, docUpdate); } - private static ReactivePanacheUpdate executeUpdate(Class entityClass, String update, Map params) { + private UpdateType executeUpdate(Class entityClass, String update, Map params) { String bindUpdate = bindUpdate(entityClass, update, params); - BsonDocument docUpdate = BsonDocument.parse(bindUpdate); + Document docUpdate = Document.parse(bindUpdate); ReactiveMongoCollection collection = mongoCollection(entityClass); - return new ReactivePanacheUpdateImpl(entityClass, docUpdate, collection); + return createUpdate(collection, entityClass, docUpdate); } - public static IllegalStateException implementationInjectionMissing() { + public 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/ReactivePanacheUpdateImpl.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactivePanacheUpdateImpl.java similarity index 69% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactivePanacheUpdateImpl.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactivePanacheUpdateImpl.java index c380211a3edbe..f8d0c502cc8ac 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactivePanacheUpdateImpl.java +++ b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/ReactivePanacheUpdateImpl.java @@ -10,12 +10,16 @@ import io.quarkus.panache.common.Parameters; import io.smallrye.mutiny.Uni; +@SuppressWarnings({ "unchecked", "rawtypes" }) public class ReactivePanacheUpdateImpl implements ReactivePanacheUpdate { - private Class entityClass; - private Bson update; - private ReactiveMongoCollection collection; - - public ReactivePanacheUpdateImpl(Class entityClass, Bson update, ReactiveMongoCollection collection) { + private final ReactiveMongoOperations operations; + private final Class entityClass; + private final Bson update; + private final ReactiveMongoCollection collection; + + public ReactivePanacheUpdateImpl(ReactiveMongoOperations operations, Class entityClass, Bson update, + ReactiveMongoCollection collection) { + this.operations = operations; this.entityClass = entityClass; this.update = update; this.collection = collection; @@ -23,14 +27,14 @@ public ReactivePanacheUpdateImpl(Class entityClass, Bson update, ReactiveMong @Override public Uni where(String query, Object... params) { - String bindQuery = ReactiveMongoOperations.bindFilter(entityClass, query, params); + String bindQuery = operations.bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); return collection.updateMany(docQuery, update).map(result -> result.getModifiedCount()); } @Override public Uni where(String query, Map params) { - String bindQuery = ReactiveMongoOperations.bindFilter(entityClass, query, params); + String bindQuery = operations.bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); return collection.updateMany(docQuery, update).map(result -> result.getModifiedCount()); } diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/BeanUtils.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/BeanUtils.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/BeanUtils.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/BeanUtils.java diff --git a/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/CommonPanacheQueryImpl.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/CommonPanacheQueryImpl.java new file mode 100644 index 0000000000000..84b76af387ca7 --- /dev/null +++ b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/CommonPanacheQueryImpl.java @@ -0,0 +1,229 @@ +package io.quarkus.mongodb.panache.runtime; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; + +import org.bson.Document; +import org.bson.conversions.Bson; + +import com.mongodb.client.FindIterable; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoCursor; +import com.mongodb.client.model.Collation; + +import io.quarkus.panache.common.Page; +import io.quarkus.panache.common.Range; +import io.quarkus.panache.common.exception.PanacheQueryException; + +public class CommonPanacheQueryImpl { + private MongoCollection collection; + private Bson mongoQuery; + private Bson sort; + private Bson projections; + + private Page page; + private Long count; + + private Range range; + + private Collation collation; + + public CommonPanacheQueryImpl(MongoCollection collection, Bson mongoQuery, Bson sort) { + this.collection = collection; + this.mongoQuery = mongoQuery; + this.sort = sort; + } + + private CommonPanacheQueryImpl(CommonPanacheQueryImpl previousQuery, Bson projections, Class documentClass) { + this.collection = previousQuery.collection.withDocumentClass(documentClass); + this.mongoQuery = previousQuery.mongoQuery; + this.sort = previousQuery.sort; + this.projections = projections; + this.page = previousQuery.page; + this.count = previousQuery.count; + this.range = previousQuery.range; + this.collation = previousQuery.collation; + } + + public CommonPanacheQueryImpl project(Class type) { + // collect field names from public fields and getters + Set fieldNames = MongoPropertyUtil.collectFields(type); + + // create the projection document + Document projections = new Document(); + for (String fieldName : fieldNames) { + projections.append(fieldName, 1); + } + + return new CommonPanacheQueryImpl<>(this, projections, type); + } + + @SuppressWarnings("unchecked") + public CommonPanacheQueryImpl page(Page page) { + this.page = page; + this.range = null; // reset the range to be able to switch from range to page + return (CommonPanacheQueryImpl) this; + } + + public CommonPanacheQueryImpl page(int pageIndex, int pageSize) { + return page(Page.of(pageIndex, pageSize)); + } + + public CommonPanacheQueryImpl nextPage() { + checkPagination(); + return page(page.next()); + } + + public CommonPanacheQueryImpl previousPage() { + checkPagination(); + return page(page.previous()); + } + + public CommonPanacheQueryImpl firstPage() { + checkPagination(); + return page(page.first()); + } + + public CommonPanacheQueryImpl lastPage() { + checkPagination(); + return page(page.index(pageCount() - 1)); + } + + public boolean hasNextPage() { + checkPagination(); + return page.index < (pageCount() - 1); + } + + public boolean hasPreviousPage() { + checkPagination(); + return page.index > 0; + } + + public int pageCount() { + checkPagination(); + long count = count(); + if (count == 0) + return 1; // a single page of zero results + return (int) Math.ceil((double) count / (double) page.size); + } + + public Page page() { + checkPagination(); + return page; + } + + private void checkPagination() { + if (page == null) { + throw new UnsupportedOperationException( + "Cannot call a page related method, " + + "call page(Page) or page(int, int) to initiate pagination first"); + } + if (range != null) { + throw new UnsupportedOperationException("Cannot call a page related method in a ranged query, " + + "call page(Page) or page(int, int) to initiate pagination first"); + } + } + + public CommonPanacheQueryImpl range(int startIndex, int lastIndex) { + this.range = Range.of(startIndex, lastIndex); + // reset the page to its default to be able to switch from page to range + this.page = null; + return (CommonPanacheQueryImpl) this; + } + + public CommonPanacheQueryImpl withCollation(Collation collation) { + this.collation = collation; + return (CommonPanacheQueryImpl) this; + } + + // Results + + @SuppressWarnings("unchecked") + public long count() { + if (count == null) { + count = collection.countDocuments(mongoQuery); + } + return count; + } + + public List list() { + return list(null); + } + + @SuppressWarnings("unchecked") + private List list(Integer limit) { + List list = new ArrayList<>(); + FindIterable find = mongoQuery == null ? collection.find() : collection.find(mongoQuery); + if (this.projections != null) { + find.projection(projections); + } + if (this.collation != null) { + find.collation(collation); + } + manageOffsets(find, limit); + MongoCursor cursor = find.sort(sort).iterator(); + + try { + while (cursor.hasNext()) { + T entity = cursor.next(); + list.add(entity); + } + } finally { + cursor.close(); + } + return list; + } + + @SuppressWarnings("unchecked") + public Stream stream() { + return (Stream) list().stream(); + } + + public T firstResult() { + List list = list(1); + return list.isEmpty() ? null : list.get(0); + } + + public Optional firstResultOptional() { + return Optional.ofNullable(firstResult()); + } + + public T singleResult() { + List list = list(2); + if (list.size() != 1) { + throw new PanacheQueryException("There should be only one result"); + } + + return list.get(0); + } + + public Optional singleResultOptional() { + List list = list(2); + if (list.size() > 1) { + throw new PanacheQueryException("There should be no more than one result"); + } + + return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); + } + + private void manageOffsets(FindIterable find, Integer limit) { + if (range != null) { + find.skip(range.getStartIndex()); + if (limit == null) { + // range is 0 based, so we add 1 to the limit + find.limit(range.getLastIndex() - range.getStartIndex() + 1); + } + } else if (page != null) { + find.skip(page.index * page.size); + if (limit == null) { + find.limit(page.size); + } + } + if (limit != null) { + find.limit(limit); + } + } +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java similarity index 65% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java index f2ea88a7f344d..a2cc1e8782e25 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java +++ b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoOperations.java @@ -32,28 +32,32 @@ import com.mongodb.client.result.DeleteResult; import io.quarkus.mongodb.panache.MongoEntity; -import io.quarkus.mongodb.panache.PanacheQuery; -import io.quarkus.mongodb.panache.PanacheUpdate; import io.quarkus.mongodb.panache.binder.NativeQueryBinder; import io.quarkus.mongodb.panache.binder.PanacheQlQueryBinder; import io.quarkus.panache.common.Parameters; import io.quarkus.panache.common.Sort; -public class MongoOperations { +@SuppressWarnings({ "rawtypes", "unchecked" }) +public abstract class MongoOperations { + public final String ID = "_id"; private static final Logger LOGGER = Logger.getLogger(MongoOperations.class); - public static final String ID = "_id"; - private static final Map defaultDatabaseName = new ConcurrentHashMap<>(); + private final Map defaultDatabaseName = new ConcurrentHashMap<>(); - // - // Instance methods + protected abstract QueryType createQuery(MongoCollection collection, Document query, Document sortDoc); + + protected abstract UpdateType createUpdate(MongoCollection collection, Class entityClass, Document docUpdate); + + protected abstract List list(QueryType queryType); - public static void persist(Object entity) { + protected abstract Stream stream(QueryType queryType); + + public void persist(Object entity) { MongoCollection collection = mongoCollection(entity); persist(collection, entity); } - public static void persist(Iterable entities) { + public void persist(Iterable entities) { // not all iterables are re-traversal, so we traverse it once for copying inside a list List objects = new ArrayList<>(); for (Object entity : entities) { @@ -68,7 +72,7 @@ public static void persist(Iterable entities) { } } - public static void persist(Object firstEntity, Object... entities) { + public void persist(Object firstEntity, Object... entities) { MongoCollection collection = mongoCollection(firstEntity); if (entities == null || entities.length == 0) { persist(collection, firstEntity); @@ -80,22 +84,22 @@ public static void persist(Object firstEntity, Object... entities) { } } - public static void persist(Stream entities) { + public void persist(Stream entities) { List objects = entities.collect(Collectors.toList()); if (objects.size() > 0) { // get the first entity to be able to retrieve the collection with it Object firstEntity = objects.get(0); MongoCollection collection = mongoCollection(firstEntity); - persist(collection, objects); + update(collection, objects); } } - public static void update(Object entity) { + public void update(Object entity) { MongoCollection collection = mongoCollection(entity); update(collection, entity); } - public static void update(Iterable entities) { + public void update(Iterable entities) { // not all iterables are re-traversal, so we traverse it once for copying inside a list List objects = new ArrayList<>(); for (Object entity : entities) { @@ -110,7 +114,7 @@ public static void update(Iterable entities) { } } - public static void update(Object firstEntity, Object... entities) { + public void update(Object firstEntity, Object... entities) { MongoCollection collection = mongoCollection(firstEntity); if (entities == null || entities.length == 0) { update(collection, firstEntity); @@ -122,7 +126,7 @@ public static void update(Object firstEntity, Object... entities) { } } - public static void update(Stream entities) { + public void update(Stream entities) { List objects = entities.collect(Collectors.toList()); if (objects.size() > 0) { // get the first entity to be able to retrieve the collection with it @@ -132,12 +136,12 @@ public static void update(Stream entities) { } } - public static void persistOrUpdate(Object entity) { + public void persistOrUpdate(Object entity) { MongoCollection collection = mongoCollection(entity); persistOrUpdate(collection, entity); } - public static void persistOrUpdate(Iterable entities) { + public void 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) { @@ -152,7 +156,7 @@ public static void persistOrUpdate(Iterable entities) { } } - public static void persistOrUpdate(Object firstEntity, Object... entities) { + public void persistOrUpdate(Object firstEntity, Object... entities) { MongoCollection collection = mongoCollection(firstEntity); if (entities == null || entities.length == 0) { persistOrUpdate(collection, firstEntity); @@ -164,7 +168,7 @@ public static void persistOrUpdate(Object firstEntity, Object... entities) { } } - public static void persistOrUpdate(Stream entities) { + public void persistOrUpdate(Stream entities) { List objects = entities.collect(Collectors.toList()); if (objects.size() > 0) { // get the first entity to be able to retrieve the collection with it @@ -174,7 +178,7 @@ public static void persistOrUpdate(Stream entities) { } } - public static void delete(Object entity) { + public void delete(Object entity) { MongoCollection collection = mongoCollection(entity); BsonDocument document = getBsonDocument(collection, entity); BsonValue id = document.get(ID); @@ -182,7 +186,7 @@ public static void delete(Object entity) { collection.deleteOne(query); } - public static MongoCollection mongoCollection(Class entityClass) { + public MongoCollection mongoCollection(Class entityClass) { MongoEntity mongoEntity = entityClass.getAnnotation(MongoEntity.class); MongoDatabase database = mongoDatabase(mongoEntity); if (mongoEntity != null && !mongoEntity.collection().isEmpty()) { @@ -191,7 +195,7 @@ public static MongoCollection mongoCollection(Class entityClass) { return database.getCollection(entityClass.getSimpleName(), entityClass); } - public static MongoDatabase mongoDatabase(Class entityClass) { + public MongoDatabase mongoDatabase(Class entityClass) { MongoEntity mongoEntity = entityClass.getAnnotation(MongoEntity.class); return mongoDatabase(mongoEntity); } @@ -199,15 +203,15 @@ public static MongoDatabase mongoDatabase(Class entityClass) { // // Private stuff - private static void persist(MongoCollection collection, Object entity) { + private void persist(MongoCollection collection, Object entity) { collection.insertOne(entity); } - private static void persist(MongoCollection collection, List entities) { + private void persist(MongoCollection collection, List entities) { collection.insertMany(entities); } - private static void update(MongoCollection collection, Object entity) { + private void update(MongoCollection collection, Object entity) { //we transform the entity as a document first BsonDocument document = getBsonDocument(collection, entity); @@ -217,13 +221,13 @@ private static void update(MongoCollection collection, Object entity) { collection.replaceOne(query, entity); } - private static void update(MongoCollection collection, List entities) { + private void update(MongoCollection collection, List entities) { for (Object entity : entities) { update(collection, entity); } } - private static void persistOrUpdate(MongoCollection collection, Object entity) { + private void persistOrUpdate(MongoCollection collection, Object entity) { //we transform the entity as a document first BsonDocument document = getBsonDocument(collection, entity); @@ -239,7 +243,7 @@ private static void persistOrUpdate(MongoCollection collection, Object entity) { } } - private static void persistOrUpdate(MongoCollection collection, List entities) { + private void persistOrUpdate(MongoCollection 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) { @@ -262,19 +266,19 @@ private static void persistOrUpdate(MongoCollection collection, List ent collection.bulkWrite(bulk); } - private static BsonDocument getBsonDocument(MongoCollection collection, Object entity) { + private BsonDocument getBsonDocument(MongoCollection 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 MongoCollection mongoCollection(Object entity) { + private MongoCollection mongoCollection(Object entity) { Class entityClass = entity.getClass(); return mongoCollection(entityClass); } - private static MongoDatabase mongoDatabase(MongoEntity entity) { + private MongoDatabase mongoDatabase(MongoEntity entity) { MongoClient mongoClient = clientFromArc(entity, MongoClient.class); if (entity != null && !entity.database().isEmpty()) { return mongoClient.getDatabase(entity.database()); @@ -283,7 +287,7 @@ private static MongoDatabase mongoDatabase(MongoEntity entity) { return mongoClient.getDatabase(databaseName); } - private static String getDefaultDatabaseName(MongoEntity entity) { + private String getDefaultDatabaseName(MongoEntity entity) { return defaultDatabaseName.computeIfAbsent(beanName(entity), new Function() { @Override public String apply(String beanName) { @@ -291,37 +295,36 @@ public String apply(String beanName) { } }); } - // // Queries - public static Object findById(Class entityClass, Object id) { + public Object findById(Class entityClass, Object id) { MongoCollection collection = mongoCollection(entityClass); return collection.find(new Document(ID, id)).first(); } - public static Optional findByIdOptional(Class entityClass, Object id) { + public Optional findByIdOptional(Class entityClass, Object id) { return Optional.ofNullable(findById(entityClass, id)); } - public static PanacheQuery find(Class entityClass, String query, Object... params) { + public QueryType find(Class entityClass, String query, Object... params) { return find(entityClass, query, null, params); } @SuppressWarnings("rawtypes") - public static PanacheQuery find(Class entityClass, String query, Sort sort, Object... params) { + public QueryType find(Class entityClass, String query, Sort sort, Object... params) { String bindQuery = bindFilter(entityClass, query, params); - BsonDocument docQuery = BsonDocument.parse(bindQuery); + Document docQuery = Document.parse(bindQuery); Document docSort = sortToDocument(sort); MongoCollection collection = mongoCollection(entityClass); - return new PanacheQueryImpl(collection, docQuery, docSort); + return createQuery(collection, docQuery, docSort); } /** * We should have a query like {'firstname': ?1, 'lastname': ?2} for native one * and like firstname = ?1 for PanacheQL one. */ - static String bindFilter(Class clazz, String query, Object[] params) { + public String bindFilter(Class clazz, String query, Object[] params) { String bindQuery = bindQuery(clazz, query, params); LOGGER.debug(bindQuery); return bindQuery; @@ -331,7 +334,7 @@ static String bindFilter(Class clazz, String query, Object[] params) { * 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 bindFilter(Class clazz, String query, Map params) { + public String bindFilter(Class clazz, String query, Map params) { String bindQuery = bindQuery(clazz, query, params); LOGGER.debug(bindQuery); return bindQuery; @@ -342,7 +345,7 @@ static String bindFilter(Class clazz, String query, Map param * and like firstname = ?1 and lastname = ?2 for PanacheQL one. * As update document needs a $set operator we add it if needed. */ - static String bindUpdate(Class clazz, String query, Object[] params) { + String bindUpdate(Class clazz, String query, Object[] params) { String bindUpdate = bindQuery(clazz, query, params); if (!bindUpdate.contains("$set")) { bindUpdate = "{'$set':" + bindUpdate + "}"; @@ -356,7 +359,7 @@ static String bindUpdate(Class clazz, String query, Object[] params) { * and like firstname = :firstname and lastname = :lastname for PanacheQL one. * As update document needs a $set operator we add it if needed. */ - static String bindUpdate(Class clazz, String query, Map params) { + String bindUpdate(Class clazz, String query, Map params) { String bindUpdate = bindQuery(clazz, query, params); if (!bindUpdate.contains("$set")) { bindUpdate = "{'$set':" + bindUpdate + "}"; @@ -365,7 +368,7 @@ static String bindUpdate(Class clazz, String query, Map param return bindUpdate; } - private static String bindQuery(Class clazz, String query, Object[] params) { + private String bindQuery(Class clazz, String query, Object[] params) { String bindQuery = null; //determine the type of the query if (query.charAt(0) == '{') { @@ -378,7 +381,7 @@ private static String bindQuery(Class clazz, String query, Object[] params) { return bindQuery; } - private static String bindQuery(Class clazz, String query, Map params) { + private String bindQuery(Class clazz, String query, Map params) { String bindQuery = null; //determine the type of the query if (query.charAt(0) == '{') { @@ -391,125 +394,125 @@ private static String bindQuery(Class clazz, String query, Map find(Class entityClass, String query, Map params) { + public QueryType find(Class entityClass, String query, Map params) { return find(entityClass, query, null, params); } @SuppressWarnings("rawtypes") - public static PanacheQuery find(Class entityClass, String query, Sort sort, Map params) { + public QueryType find(Class entityClass, String query, Sort sort, Map params) { String bindQuery = bindFilter(entityClass, query, params); - BsonDocument docQuery = BsonDocument.parse(bindQuery); + Document docQuery = Document.parse(bindQuery); Document docSort = sortToDocument(sort); MongoCollection collection = mongoCollection(entityClass); - return new PanacheQueryImpl(collection, docQuery, docSort); + return createQuery(collection, docQuery, docSort); } - public static PanacheQuery find(Class entityClass, String query, Parameters params) { + public QueryType find(Class entityClass, String query, Parameters params) { return find(entityClass, query, null, params.map()); } - public static PanacheQuery find(Class entityClass, String query, Sort sort, Parameters params) { + public QueryType find(Class entityClass, String query, Sort sort, Parameters params) { return find(entityClass, query, sort, params.map()); } @SuppressWarnings("rawtypes") - public static PanacheQuery find(Class entityClass, Document query, Sort sort) { + public QueryType find(Class entityClass, Document query, Sort sort) { MongoCollection collection = mongoCollection(entityClass); Document sortDoc = sortToDocument(sort); - return new PanacheQueryImpl(collection, query, sortDoc); + return createQuery(collection, query, sortDoc); } - public static PanacheQuery find(Class entityClass, Document query, Document sort) { + public QueryType find(Class entityClass, Document query, Document sort) { MongoCollection collection = mongoCollection(entityClass); - return new PanacheQueryImpl(collection, query, sort); + return createQuery(collection, query, sort); } - public static PanacheQuery find(Class entityClass, Document query) { + public QueryType find(Class entityClass, Document query) { return find(entityClass, query, (Document) null); } - public static List list(Class entityClass, String query, Object... params) { - return find(entityClass, query, params).list(); + public List list(Class entityClass, String query, Object... params) { + return list(find(entityClass, query, params)); } - public static List list(Class entityClass, String query, Sort sort, Object... params) { - return find(entityClass, query, sort, params).list(); + public List list(Class entityClass, String query, Sort sort, Object... params) { + return list(find(entityClass, query, sort, params)); } - public static List list(Class entityClass, String query, Map params) { - return find(entityClass, query, params).list(); + public List list(Class entityClass, String query, Map params) { + return list(find(entityClass, query, params)); } - public static List list(Class entityClass, String query, Sort sort, Map params) { - return find(entityClass, query, sort, params).list(); + public List list(Class entityClass, String query, Sort sort, Map params) { + return list(find(entityClass, query, sort, params)); } - public static List list(Class entityClass, String query, Parameters params) { - return find(entityClass, query, params).list(); + public List list(Class entityClass, String query, Parameters params) { + return list(find(entityClass, query, params)); } - public static List list(Class entityClass, String query, Sort sort, Parameters params) { - return find(entityClass, query, sort, params).list(); + public List list(Class entityClass, String query, Sort sort, Parameters params) { + return list(find(entityClass, query, sort, params)); } //specific Mongo query - public static List list(Class entityClass, Document query) { - return find(entityClass, query).list(); + public List list(Class entityClass, Document query) { + return list(find(entityClass, query)); } //specific Mongo query - public static List list(Class entityClass, Document query, Document sort) { - return find(entityClass, query, sort).list(); + public List list(Class entityClass, Document query, Document sort) { + return list(find(entityClass, query, sort)); } - public static Stream stream(Class entityClass, String query, Object... params) { - return find(entityClass, query, params).stream(); + public Stream stream(Class entityClass, String query, Object... params) { + return stream(find(entityClass, query, params)); } - public static Stream stream(Class entityClass, String query, Sort sort, Object... params) { - return find(entityClass, query, sort, params).stream(); + public Stream stream(Class entityClass, String query, Sort sort, Object... params) { + return stream(find(entityClass, query, sort, params)); } - public static Stream stream(Class entityClass, String query, Map params) { - return find(entityClass, query, params).stream(); + public Stream stream(Class entityClass, String query, Map params) { + return stream(find(entityClass, query, params)); } - public static Stream stream(Class entityClass, String query, Sort sort, Map params) { - return find(entityClass, query, sort, params).stream(); + public Stream stream(Class entityClass, String query, Sort sort, Map params) { + return stream(find(entityClass, query, sort, params)); } - public static Stream stream(Class entityClass, String query, Parameters params) { - return find(entityClass, query, params).stream(); + public Stream stream(Class entityClass, String query, Parameters params) { + return stream(find(entityClass, query, params)); } - public static Stream stream(Class entityClass, String query, Sort sort, Parameters params) { - return find(entityClass, query, sort, params).stream(); + public Stream stream(Class entityClass, String query, Sort sort, Parameters params) { + return stream(find(entityClass, query, sort, params)); } //specific Mongo query - public static Stream stream(Class entityClass, Document query) { - return find(entityClass, query).stream(); + public Stream stream(Class entityClass, Document query) { + return stream(find(entityClass, query)); } //specific Mongo query - public static Stream stream(Class entityClass, Document query, Document sort) { - return find(entityClass, query, sort).stream(); + public Stream stream(Class entityClass, Document query, Document sort) { + return stream(find(entityClass, query, sort)); } @SuppressWarnings("rawtypes") - public static PanacheQuery findAll(Class entityClass) { + public QueryType findAll(Class entityClass) { MongoCollection collection = mongoCollection(entityClass); - return new PanacheQueryImpl(collection, null, null); + return createQuery(collection, null, null); } @SuppressWarnings("rawtypes") - public static PanacheQuery findAll(Class entityClass, Sort sort) { + public QueryType findAll(Class entityClass, Sort sort) { MongoCollection collection = mongoCollection(entityClass); Document sortDoc = sortToDocument(sort); - return new PanacheQueryImpl(collection, null, sortDoc); + return createQuery(collection, null, sortDoc); } - private static Document sortToDocument(Sort sort) { + private Document sortToDocument(Sort sort) { if (sort == null) { return null; } @@ -521,114 +524,112 @@ private static Document sortToDocument(Sort sort) { return sortDoc; } - public static List listAll(Class entityClass) { - return findAll(entityClass).list(); + public List listAll(Class entityClass) { + return list(findAll(entityClass)); } - public static List listAll(Class entityClass, Sort sort) { - return findAll(entityClass, sort).list(); + public List listAll(Class entityClass, Sort sort) { + return list(findAll(entityClass, sort)); } - public static Stream streamAll(Class entityClass) { - return findAll(entityClass).stream(); + public Stream streamAll(Class entityClass) { + return stream(findAll(entityClass)); } - public static Stream streamAll(Class entityClass, Sort sort) { - return findAll(entityClass, sort).stream(); + public Stream streamAll(Class entityClass, Sort sort) { + return stream(findAll(entityClass, sort)); } - public static long count(Class entityClass) { + public long count(Class entityClass) { MongoCollection collection = mongoCollection(entityClass); return collection.countDocuments(); } - public static long count(Class entityClass, String query, Object... params) { + public long count(Class entityClass, String query, Object... params) { String bindQuery = bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); MongoCollection collection = mongoCollection(entityClass); return collection.countDocuments(docQuery); } - public static long count(Class entityClass, String query, Map params) { + public long count(Class entityClass, String query, Map params) { String bindQuery = bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); MongoCollection collection = mongoCollection(entityClass); return collection.countDocuments(docQuery); } - public static long count(Class entityClass, String query, Parameters params) { + public long count(Class entityClass, String query, Parameters params) { return count(entityClass, query, params.map()); } //specific Mongo query - public static long count(Class entityClass, Document query) { + public long count(Class entityClass, Document query) { MongoCollection collection = mongoCollection(entityClass); return collection.countDocuments(query); } - public static long deleteAll(Class entityClass) { + public long deleteAll(Class entityClass) { MongoCollection collection = mongoCollection(entityClass); return collection.deleteMany(new Document()).getDeletedCount(); } - public static boolean deleteById(Class entityClass, Object id) { + public boolean deleteById(Class entityClass, Object id) { MongoCollection collection = mongoCollection(entityClass); Document query = new Document().append(ID, id); DeleteResult results = collection.deleteOne(query); return results.getDeletedCount() == 1; } - public static long delete(Class entityClass, String query, Object... params) { + public long delete(Class entityClass, String query, Object... params) { String bindQuery = bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); MongoCollection collection = mongoCollection(entityClass); return collection.deleteMany(docQuery).getDeletedCount(); } - public static long delete(Class entityClass, String query, Map params) { + public long delete(Class entityClass, String query, Map params) { String bindQuery = bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); MongoCollection collection = mongoCollection(entityClass); return collection.deleteMany(docQuery).getDeletedCount(); } - public static long delete(Class entityClass, String query, Parameters params) { + public long delete(Class entityClass, String query, Parameters params) { return delete(entityClass, query, params.map()); } //specific Mongo query - public static long delete(Class entityClass, Document query) { + public long delete(Class entityClass, Document query) { MongoCollection collection = mongoCollection(entityClass); return collection.deleteMany(query).getDeletedCount(); } - public static PanacheUpdate update(Class entityClass, String update, Map params) { + public UpdateType update(Class entityClass, String update, Map params) { return executeUpdate(entityClass, update, params); } - public static PanacheUpdate update(Class entityClass, String update, Parameters params) { + public UpdateType update(Class entityClass, String update, Parameters params) { return update(entityClass, update, params.map()); } - public static PanacheUpdate update(Class entityClass, String update, Object... params) { + public UpdateType update(Class entityClass, String update, Object... params) { return executeUpdate(entityClass, update, params); } - private static PanacheUpdate executeUpdate(Class entityClass, String update, Object... params) { + private UpdateType executeUpdate(Class entityClass, String update, Object... params) { String bindUpdate = bindUpdate(entityClass, update, params); - BsonDocument docUpdate = BsonDocument.parse(bindUpdate); - MongoCollection collection = mongoCollection(entityClass); - return new PanacheUpdateImpl(entityClass, docUpdate, collection); + Document docUpdate = Document.parse(bindUpdate); + return createUpdate(mongoCollection(entityClass), entityClass, docUpdate); } - private static PanacheUpdate executeUpdate(Class entityClass, String update, Map params) { + private UpdateType executeUpdate(Class entityClass, String update, Map params) { String bindUpdate = bindUpdate(entityClass, update, params); - BsonDocument docUpdate = BsonDocument.parse(bindUpdate); - MongoCollection collection = mongoCollection(entityClass); - return new PanacheUpdateImpl(entityClass, docUpdate, collection); + Document docUpdate = Document.parse(bindUpdate); + return createUpdate(mongoCollection(entityClass), entityClass, docUpdate); } - public static IllegalStateException implementationInjectionMissing() { + public 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/runtime/MongoPropertyUtil.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoPropertyUtil.java similarity index 100% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoPropertyUtil.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/MongoPropertyUtil.java diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheUpdateImpl.java b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheUpdateImpl.java similarity index 77% rename from extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheUpdateImpl.java rename to extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheUpdateImpl.java index effeae31cdbb5..1cf9e9d7bfac6 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheUpdateImpl.java +++ b/extensions/panache/mongodb-panache-common/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheUpdateImpl.java @@ -11,11 +11,13 @@ import io.quarkus.panache.common.Parameters; public class PanacheUpdateImpl implements PanacheUpdate { + private MongoOperations operations; private Class entityClass; private Bson update; private MongoCollection collection; - public PanacheUpdateImpl(Class entityClass, Bson update, MongoCollection collection) { + public PanacheUpdateImpl(MongoOperations operations, Class entityClass, Bson update, MongoCollection collection) { + this.operations = operations; this.entityClass = entityClass; this.update = update; this.collection = collection; @@ -23,14 +25,14 @@ public PanacheUpdateImpl(Class entityClass, Bson update, MongoCollection coll @Override public long where(String query, Object... params) { - String bindQuery = MongoOperations.bindFilter(entityClass, query, params); + String bindQuery = operations.bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); return collection.updateMany(docQuery, update).getModifiedCount(); } @Override public long where(String query, Map params) { - String bindQuery = MongoOperations.bindFilter(entityClass, query, params); + String bindQuery = operations.bindFilter(entityClass, query, params); BsonDocument docQuery = BsonDocument.parse(bindQuery); return collection.updateMany(docQuery, update).getModifiedCount(); } diff --git a/extensions/panache/mongodb-panache-kotlin/deployment/pom.xml b/extensions/panache/mongodb-panache-kotlin/deployment/pom.xml new file mode 100644 index 0000000000000..d5f09d2c73332 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/deployment/pom.xml @@ -0,0 +1,63 @@ + + + + quarkus-mongodb-panache-kotlin-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-mongodb-panache-kotlin-deployment + Quarkus - MongoDB with Panache - Kotlin Deployment + + + + io.quarkus + quarkus-mongodb-panache-kotlin + + + io.quarkus + quarkus-mongodb-panache-common-deployment + + + io.quarkus + quarkus-mongodb-client-deployment + + + io.quarkus + quarkus-jsonb-spi + + + io.quarkus + quarkus-jackson-spi + + + + io.quarkus + quarkus-junit5-internal + test + + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${project.version} + + + + + + + + + \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinGenerator.java b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinGenerator.java new file mode 100644 index 0000000000000..51967b9c7d08b --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinGenerator.java @@ -0,0 +1,205 @@ +package io.quarkus.mongodb.panache.kotlin.deployment; + +import static io.quarkus.deployment.util.AsmUtil.getDescriptor; +import static io.quarkus.deployment.util.AsmUtil.getSignature; +import static io.quarkus.mongodb.panache.deployment.BasePanacheMongoResourceProcessor.OBJECT_SIGNATURE; +import static java.util.Arrays.asList; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.CHECKCAST; +import static org.objectweb.asm.Opcodes.GETSTATIC; +import static org.objectweb.asm.Opcodes.ILOAD; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static org.objectweb.asm.Type.ARRAY; +import static org.objectweb.asm.Type.getMethodDescriptor; +import static org.objectweb.asm.Type.getType; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.StringJoiner; +import java.util.function.Function; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import io.quarkus.deployment.util.AsmUtil; +import io.quarkus.deployment.util.JandexUtil; +import io.quarkus.mongodb.panache.deployment.ByteCodeType; +import io.quarkus.mongodb.panache.deployment.TypeBundle; +import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; + +public class KotlinGenerator { + public static final ByteCodeType NOT_NULL = new ByteCodeType(NotNull.class); + private static final ByteCodeType CLASS = new ByteCodeType(Class.class); + + private final Function argMapper; + private final ClassVisitor cv; + private final List methods = new ArrayList<>(); + private final Map typeArguments; + private final TypeBundle types; + private final Map typeParameters; + + public KotlinGenerator(ClassVisitor classVisitor, Map typeParameters, + Map typeArguments, TypeBundle types) { + this.cv = classVisitor; + this.typeParameters = typeParameters; + this.typeArguments = typeArguments; + this.types = types; + this.argMapper = type -> typeArguments.get(type); + } + + public static ByteCodeType[] findEntityTypeArguments(IndexView indexView, String repositoryClassName, + DotName repositoryDotName) { + for (ClassInfo classInfo : indexView.getAllKnownImplementors(repositoryDotName)) { + if (repositoryClassName.equals(classInfo.name().toString())) { + return recursivelyFindEntityTypeArguments(indexView, classInfo.name(), repositoryDotName); + } + } + + return null; + } + + public static ByteCodeType[] recursivelyFindEntityTypeArguments(IndexView indexView, DotName clazz, + DotName repositoryDotName) { + if (clazz.equals(JandexUtil.DOTNAME_OBJECT)) { + return null; + } + + List typeParameters = JandexUtil + .resolveTypeParameters(clazz, repositoryDotName, indexView); + if (typeParameters.isEmpty()) + throw new IllegalStateException( + "Failed to find supertype " + repositoryDotName + " from entity class " + clazz); + return new ByteCodeType[] { + new ByteCodeType(typeParameters.get(0)), + new ByteCodeType(typeParameters.get(1)) + }; + } + + public void add(MethodInfo methodInfo) { + methods.add(methodInfo); + } + + private void addNullityChecks(MethodVisitor mv, List parameters) { + for (int i = 0; i < parameters.size(); i++) { + Type parameter = parameters.get(i); + if (parameter.hasAnnotation(NOT_NULL.dotName())) { + mv.visitVarInsn(ALOAD, i + 1); + mv.visitLdcInsn(parameter.name()); + mv.visitMethodInsn(INVOKESTATIC, "kotlin/jvm/internal/Intrinsics", "checkParameterIsNotNull", + "(Ljava/lang/Object;Ljava/lang/String;)V", false); + } + } + } + + public void generate() { + methods.forEach(m -> generate(m)); + } + + public void generate(MethodInfo method) { + // Note: we can't use SYNTHETIC here because otherwise Mockito will never mock these methods + MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PUBLIC, method.name(), + getDescriptor(method, argMapper), getSignature(method, argMapper), null); + + List parameters = method.parameters(); + for (int i = 0; i < parameters.size(); i++) { + mv.visitParameter(method.parameterName(i), 0 /* modifiers */); + } + + addNullityChecks(mv, parameters); + + loadOperationsReference(mv); + + loadArguments(mv, parameters); + invokeOperation(mv, method, parameters); + + mv.visitMaxs(parameters.size() + 1, parameters.size()); + + for (int i = 0; i < parameters.size(); i++) { + Type argument = parameters.get(i); + for (AnnotationInstance annotation : argument.annotations()) { + mv.visitParameterAnnotation(i, "L" + annotation.name() + ";", true); + } + } + } + + private void invokeOperation(MethodVisitor mv, MethodInfo method, List parameters) { + String operationDescriptor; + + AnnotationInstance bridge = method.annotation(PanacheEntityEnhancer.DOTNAME_GENERATE_BRIDGE); + AnnotationValue targetReturnTypeErased = bridge.value("targetReturnTypeErased"); + boolean erased = targetReturnTypeErased != null && targetReturnTypeErased.asBoolean(); + + StringJoiner joiner = new StringJoiner("", "(", ")"); + joiner.add(CLASS.descriptor()); + for (Type parameter : parameters) { + if (parameter.kind() != Type.Kind.PRIMITIVE) { + String descriptor = getDescriptor(parameter, argMapper); + if (typeArguments.containsValue(descriptor)) { + descriptor = OBJECT_SIGNATURE; + } + joiner.add(descriptor); + } else { + joiner.add(OBJECT_SIGNATURE); + } + } + + List names = asList(types.queryType().dotName().toString(), types.updateType().dotName().toString()); + operationDescriptor = joiner + + (erased || names.contains(method.returnType().name().toString()) + ? OBJECT_SIGNATURE + : getDescriptor(method.returnType(), argMapper)); + + mv.visitMethodInsn(INVOKEVIRTUAL, types.operations().internalName(), method.name(), + operationDescriptor, false); + if (method.returnType().kind() != Type.Kind.PRIMITIVE) { + Type type = method.returnType(); + String cast; + if (type.kind() == Type.Kind.TYPE_VARIABLE) { + String applied = argMapper.apply(type.asTypeVariable().identifier()); + cast = applied.substring(1, applied.length() - 1); + } else { + cast = type.name().toString().replace('.', '/'); + } + mv.visitTypeInsn(CHECKCAST, cast); + } + mv.visitInsn(AsmUtil.getReturnInstruction(method.returnType())); + } + + private void loadArguments(MethodVisitor mv, List parameters) { + mv.visitLdcInsn(org.objectweb.asm.Type.getType(typeArguments.get("Entity"))); + for (int i = 0; i < parameters.size(); i++) { + Type methodParameter = parameters.get(i); + org.objectweb.asm.Type parameter; + if (methodParameter.kind() == Type.Kind.TYPE_VARIABLE) { + parameter = typeParameters.get(parameters.get(i).asTypeVariable().identifier()).type(); + } else { + parameter = getType(getDescriptor(methodParameter, s -> null)); + } + mv.visitVarInsn(parameter.getOpcode(ILOAD), i + 1); + if (parameter.getSort() < ARRAY) { + org.objectweb.asm.Type wrapper = AsmUtil.autobox(parameter); + mv.visitMethodInsn(INVOKESTATIC, wrapper.getInternalName(), "valueOf", + getMethodDescriptor(wrapper, parameter), false); + } + + } + } + + private void loadOperationsReference(MethodVisitor mv) { + mv.visitFieldInsn(GETSTATIC, types.entityBase().internalName(), "Companion", + types.entityBaseCompanion().descriptor()); + mv.visitMethodInsn(INVOKEVIRTUAL, types.entityBaseCompanion().internalName(), "getOperations", + "()" + types.operations().descriptor(), false); + } +} diff --git a/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinImperativeTypeBundle.java b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinImperativeTypeBundle.java new file mode 100644 index 0000000000000..9cc932c72e32c --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinImperativeTypeBundle.java @@ -0,0 +1,66 @@ +package io.quarkus.mongodb.panache.kotlin.deployment; + +import io.quarkus.mongodb.panache.PanacheUpdate; +import io.quarkus.mongodb.panache.deployment.ByteCodeType; +import io.quarkus.mongodb.panache.deployment.TypeBundle; +import io.quarkus.mongodb.panache.kotlin.PanacheMongoCompanion; +import io.quarkus.mongodb.panache.kotlin.PanacheMongoCompanionBase; +import io.quarkus.mongodb.panache.kotlin.PanacheMongoEntity; +import io.quarkus.mongodb.panache.kotlin.PanacheMongoEntityBase; +import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepository; +import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepositoryBase; +import io.quarkus.mongodb.panache.kotlin.PanacheQuery; +import io.quarkus.mongodb.panache.kotlin.runtime.KotlinMongoOperations; + +class KotlinImperativeTypeBundle implements TypeBundle { + + @Override + public ByteCodeType entity() { + return new ByteCodeType(PanacheMongoEntity.class); + } + + @Override + public ByteCodeType entityBase() { + return new ByteCodeType(PanacheMongoEntityBase.class); + } + + @Override + public ByteCodeType entityBaseCompanion() { + return new ByteCodeType(PanacheMongoEntityBase.Companion.class); + } + + @Override + public ByteCodeType entityCompanion() { + return new ByteCodeType(PanacheMongoCompanion.class); + } + + @Override + public ByteCodeType entityCompanionBase() { + return new ByteCodeType(PanacheMongoCompanionBase.class); + } + + @Override + public ByteCodeType operations() { + return new ByteCodeType(KotlinMongoOperations.class); + } + + @Override + public ByteCodeType queryType() { + return new ByteCodeType(PanacheQuery.class); + } + + @Override + public ByteCodeType repository() { + return new ByteCodeType(PanacheMongoRepository.class); + } + + @Override + public ByteCodeType repositoryBase() { + return new ByteCodeType(PanacheMongoRepositoryBase.class); + } + + @Override + public ByteCodeType updateType() { + return new ByteCodeType(PanacheUpdate.class); + } +} diff --git a/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinPanacheMongoEntityEnhancer.java b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinPanacheMongoEntityEnhancer.java new file mode 100644 index 0000000000000..e2eda00f666ed --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinPanacheMongoEntityEnhancer.java @@ -0,0 +1,55 @@ +package io.quarkus.mongodb.panache.kotlin.deployment; + +import static io.quarkus.mongodb.panache.deployment.BasePanacheMongoResourceProcessor.BSON_IGNORE; + +import java.lang.reflect.Modifier; +import java.util.List; + +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 io.quarkus.gizmo.DescriptorUtils; +import io.quarkus.mongodb.panache.deployment.TypeBundle; +import io.quarkus.mongodb.panache.deployment.visitors.PanacheMongoEntityClassVisitor; +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; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; + +public class KotlinPanacheMongoEntityEnhancer extends PanacheEntityEnhancer>> { + private final TypeBundle types; + + public KotlinPanacheMongoEntityEnhancer(IndexView index, List methodCustomizers, + TypeBundle types) { + super(index, methodCustomizers); + modelInfo = new MetamodelInfo<>(); + this.types = types; + } + + @Override + public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { + return new PanacheMongoEntityClassVisitor(className, outputClassVisitor, this.modelInfo, + this.indexView.getClassByName(this.types.entityBase().dotName()), + this.indexView.getClassByName(DotName.createSimple(className)), + this.methodCustomizers, this.types); + } + + @Override + public void collectFields(ClassInfo classInfo) { + EntityModel entityModel = new EntityModel<>(classInfo); + for (FieldInfo fieldInfo : classInfo.fields()) { + String name = fieldInfo.name(); + if (Modifier.isPublic(fieldInfo.flags()) + && !Modifier.isStatic(fieldInfo.flags()) + && !fieldInfo.hasAnnotation(BSON_IGNORE)) { + entityModel.addField(new EntityField(name, DescriptorUtils.typeToString(fieldInfo.type()))); + } + } + modelInfo.addEntityModel(entityModel); + } + +} diff --git a/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinPanacheMongoRepositoryEnhancer.java b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinPanacheMongoRepositoryEnhancer.java new file mode 100644 index 0000000000000..210de669dd7f8 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinPanacheMongoRepositoryEnhancer.java @@ -0,0 +1,28 @@ +package io.quarkus.mongodb.panache.kotlin.deployment; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.IndexView; +import org.objectweb.asm.ClassVisitor; + +import io.quarkus.mongodb.panache.deployment.TypeBundle; +import io.quarkus.mongodb.panache.kotlin.deployment.visitors.KotlinPanacheMongoRepositoryClassVisitor; +import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; + +public class KotlinPanacheMongoRepositoryEnhancer extends PanacheRepositoryEnhancer { + private TypeBundle types; + + public KotlinPanacheMongoRepositoryEnhancer(IndexView index, TypeBundle types) { + super(index, types.repositoryBase().dotName()); + this.types = types; + } + + @Override + public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { + return new KotlinPanacheMongoRepositoryClassVisitor(this.indexView, outputClassVisitor, className, types); + } + + @Override + public boolean skipRepository(ClassInfo classInfo) { + return false; + } +} diff --git a/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinPanacheMongoResourceProcessor.java b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinPanacheMongoResourceProcessor.java new file mode 100644 index 0000000000000..6c1c2cd8eede0 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinPanacheMongoResourceProcessor.java @@ -0,0 +1,144 @@ +package io.quarkus.mongodb.panache.kotlin.deployment; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.jboss.jandex.ClassInfo; + +import io.quarkus.deployment.Capability; +import io.quarkus.deployment.Feature; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.ApplicationIndexBuildItem; +import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; +import io.quarkus.deployment.builditem.CapabilityBuildItem; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.mongodb.panache.deployment.BasePanacheMongoResourceProcessor; +import io.quarkus.mongodb.panache.deployment.PropertyMappingClassBuildStep; +import io.quarkus.mongodb.panache.deployment.TypeBundle; +import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem; +import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; + +public class KotlinPanacheMongoResourceProcessor extends BasePanacheMongoResourceProcessor { + public static final KotlinImperativeTypeBundle IMPERATIVE_TYPE_BUNDLE = new KotlinImperativeTypeBundle(); + public static final KotlinReactiveTypeBundle REACTIVE_TYPE_BUNDLE = new KotlinReactiveTypeBundle(); + + @BuildStep + public void buildImperativeCompanions(CombinedIndexBuildItem index, ApplicationIndexBuildItem applicationIndex, + BuildProducer transformers, + BuildProducer reflectiveClass, + BuildProducer propertyMappingClass, + List methodCustomizersBuildItems) { + + List methodCustomizers = methodCustomizersBuildItems.stream() + .map(bi -> bi.getMethodCustomizer()).collect(Collectors.toList()); + + processCompanions(index, transformers, reflectiveClass, propertyMappingClass, + createCompanionEnhancer(index, methodCustomizers), + getImperativeTypeBundle()); + } + + @BuildStep + public void buildReactiveCompanions(CombinedIndexBuildItem index, + BuildProducer reflectiveClass, + BuildProducer propertyMappingClass, + BuildProducer transformers, + List methodCustomizersBuildItems) { + + List methodCustomizers = methodCustomizersBuildItems.stream() + .map(bi -> bi.getMethodCustomizer()).collect(Collectors.toList()); + + processCompanions(index, transformers, reflectiveClass, propertyMappingClass, + createReactiveCompanionEnhancer(index, methodCustomizers), + getReactiveTypeBundle()); + } + + protected KotlinImperativeTypeBundle getImperativeTypeBundle() { + return IMPERATIVE_TYPE_BUNDLE; + } + + protected KotlinReactiveTypeBundle getReactiveTypeBundle() { + return REACTIVE_TYPE_BUNDLE; + } + + @BuildStep + protected CapabilityBuildItem capability() { + return new CapabilityBuildItem(Capability.MONGODB_PANACHE_KOTLIN); + } + + @BuildStep + protected FeatureBuildItem featureBuildItem() { + return new FeatureBuildItem(Feature.MONGODB_PANACHE_KOTLIN); + } + + @Override + public PanacheEntityEnhancer createEntityEnhancer(CombinedIndexBuildItem index, + List methodCustomizers) { + return new KotlinPanacheMongoEntityEnhancer(index.getIndex(), methodCustomizers, getImperativeTypeBundle()); + } + + private PanacheMongoCompanionEnhancer createCompanionEnhancer(CombinedIndexBuildItem index, + List methodCustomizers) { + return new PanacheMongoCompanionEnhancer(index.getIndex(), methodCustomizers, getImperativeTypeBundle()); + } + + private PanacheMongoCompanionEnhancer createReactiveCompanionEnhancer(CombinedIndexBuildItem index, + List methodCustomizers) { + return new PanacheMongoCompanionEnhancer(index.getIndex(), methodCustomizers, getReactiveTypeBundle()); + } + + @Override + public PanacheEntityEnhancer createReactiveEntityEnhancer(CombinedIndexBuildItem index, + List methodCustomizers) { + return new KotlinPanacheMongoEntityEnhancer(index.getIndex(), methodCustomizers, getImperativeTypeBundle()); + } + + @Override + public PanacheRepositoryEnhancer createRepositoryEnhancer(CombinedIndexBuildItem index) { + return new KotlinPanacheMongoRepositoryEnhancer(index.getIndex(), getImperativeTypeBundle()); + } + + @Override + public PanacheRepositoryEnhancer createReactiveRepositoryEnhancer(CombinedIndexBuildItem index) { + return new KotlinPanacheMongoRepositoryEnhancer(index.getIndex(), getReactiveTypeBundle()); + } + + private void processCompanions(CombinedIndexBuildItem index, + BuildProducer transformers, + BuildProducer reflectiveClass, + BuildProducer propertyMappingClass, + PanacheEntityEnhancer entityEnhancer, TypeBundle typeBundle) { + + 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().getAllKnownImplementors(typeBundle.entityCompanionBase().dotName())) { + if (classInfo.name().equals(typeBundle.entityCompanion().dotName())) { + continue; + } + if (modelClasses.add(classInfo.name().toString())) + entityEnhancer.collectFields(classInfo); + } + for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(typeBundle.entityCompanion().dotName())) { + if (modelClasses.add(classInfo.name().toString())) + entityEnhancer.collectFields(classInfo); + } + + // iterate over all the entity classes + for (String modelClass : modelClasses) { + transformers.produce(new BytecodeTransformerBuildItem(modelClass, entityEnhancer)); + + //register for reflection entity classes + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, modelClass)); + + // Register for building the property mapping cache + propertyMappingClass.produce(new PropertyMappingClassBuildStep(modelClass)); + } + } +} diff --git a/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinReactiveTypeBundle.java b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinReactiveTypeBundle.java new file mode 100644 index 0000000000000..e5d58c47549a2 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/KotlinReactiveTypeBundle.java @@ -0,0 +1,66 @@ +package io.quarkus.mongodb.panache.kotlin.deployment; + +import io.quarkus.mongodb.panache.deployment.ByteCodeType; +import io.quarkus.mongodb.panache.deployment.TypeBundle; +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoCompanion; +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoCompanionBase; +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoEntity; +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoEntityBase; +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoRepository; +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoRepositoryBase; +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheQuery; +import io.quarkus.mongodb.panache.kotlin.reactive.runtime.KotlinReactiveMongoOperations; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheUpdate; + +class KotlinReactiveTypeBundle implements TypeBundle { + @Override + public ByteCodeType entity() { + return new ByteCodeType(ReactivePanacheMongoEntity.class); + } + + @Override + public ByteCodeType entityBase() { + return new ByteCodeType(ReactivePanacheMongoEntityBase.class); + } + + @Override + public ByteCodeType entityBaseCompanion() { + return new ByteCodeType(ReactivePanacheMongoEntityBase.Companion.class); + } + + @Override + public ByteCodeType entityCompanion() { + return new ByteCodeType(ReactivePanacheMongoCompanion.class); + } + + @Override + public ByteCodeType entityCompanionBase() { + return new ByteCodeType(ReactivePanacheMongoCompanionBase.class); + } + + @Override + public ByteCodeType operations() { + return new ByteCodeType(KotlinReactiveMongoOperations.class); + } + + @Override + public ByteCodeType queryType() { + return new ByteCodeType(ReactivePanacheQuery.class); + } + + @Override + public ByteCodeType repository() { + return new ByteCodeType(ReactivePanacheMongoRepository.class); + } + + @Override + public ByteCodeType repositoryBase() { + return new ByteCodeType(ReactivePanacheMongoRepositoryBase.class); + } + + @Override + public ByteCodeType updateType() { + return new ByteCodeType(ReactivePanacheUpdate.class); + } + +} diff --git a/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/PanacheMongoCompanionEnhancer.java b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/PanacheMongoCompanionEnhancer.java new file mode 100644 index 0000000000000..176d7cd5b328d --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/PanacheMongoCompanionEnhancer.java @@ -0,0 +1,53 @@ +package io.quarkus.mongodb.panache.kotlin.deployment; + +import static io.quarkus.mongodb.panache.deployment.BasePanacheMongoResourceProcessor.BSON_IGNORE; + +import java.lang.reflect.Modifier; +import java.util.List; + +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 io.quarkus.gizmo.DescriptorUtils; +import io.quarkus.mongodb.panache.deployment.TypeBundle; +import io.quarkus.mongodb.panache.kotlin.deployment.visitors.PanacheMongoCompanionClassVisitor; +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; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; + +public class PanacheMongoCompanionEnhancer extends PanacheEntityEnhancer>> { + private TypeBundle types; + + public PanacheMongoCompanionEnhancer(IndexView index, List methodCustomizers, + TypeBundle types) { + super(index, methodCustomizers); + this.types = types; + modelInfo = new MetamodelInfo<>(); + } + + @Override + public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { + return new PanacheMongoCompanionClassVisitor(outputClassVisitor, + indexView.getClassByName(DotName.createSimple(className)), indexView, types); + } + + @Override + public void collectFields(ClassInfo classInfo) { + EntityModel entityModel = new EntityModel<>(classInfo); + for (FieldInfo fieldInfo : classInfo.fields()) { + String name = fieldInfo.name(); + if (Modifier.isPublic(fieldInfo.flags()) + && !Modifier.isStatic(fieldInfo.flags()) + && !fieldInfo.hasAnnotation(BSON_IGNORE)) { + entityModel.addField(new EntityField(name, DescriptorUtils.typeToString(fieldInfo.type()))); + } + } + modelInfo.addEntityModel(entityModel); + } + +} diff --git a/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/visitors/KotlinPanacheMongoRepositoryClassVisitor.java b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/visitors/KotlinPanacheMongoRepositoryClassVisitor.java new file mode 100644 index 0000000000000..6be6cd5574f47 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/visitors/KotlinPanacheMongoRepositoryClassVisitor.java @@ -0,0 +1,107 @@ +package io.quarkus.mongodb.panache.kotlin.deployment.visitors; + +import static io.quarkus.deployment.util.AsmUtil.getDescriptor; +import static io.quarkus.mongodb.panache.kotlin.deployment.KotlinGenerator.findEntityTypeArguments; + +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +import io.quarkus.mongodb.panache.deployment.ByteCodeType; +import io.quarkus.mongodb.panache.deployment.TypeBundle; +import io.quarkus.mongodb.panache.kotlin.deployment.KotlinGenerator; +import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; +import io.quarkus.panache.common.deployment.visitors.PanacheRepositoryClassVisitor; + +public class KotlinPanacheMongoRepositoryClassVisitor extends PanacheRepositoryClassVisitor { + private final TypeBundle types; + private KotlinGenerator generator; + final Map toGenerate = new TreeMap<>(); + final Map toElide = new TreeMap<>(); + + public KotlinPanacheMongoRepositoryClassVisitor(IndexView indexView, ClassVisitor outputClassVisitor, String className, + TypeBundle types) { + super(className, outputClassVisitor, indexView); + this.types = types; + } + + @Override + protected final DotName getPanacheRepositoryDotName() { + return types.repository().dotName(); + } + + @Override + protected final DotName getPanacheRepositoryBaseDotName() { + return types.repositoryBase().dotName(); + } + + @Override + protected final String getPanacheOperationsInternalName() { + return types.operations().internalName(); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + final String repositoryClassName = name.replace('/', '.'); + + ByteCodeType[] foundTypeArguments = findEntityTypeArguments(indexView, repositoryClassName, + getPanacheRepositoryBaseDotName()); + + ByteCodeType idType = foundTypeArguments[1].unbox(); + typeArguments.put("Id", idType.descriptor()); + + Map typeParameters = new HashMap<>(); + typeParameters.put("Entity", foundTypeArguments[0]); + typeParameters.put("Id", idType); + + daoClassInfo + .methods() + .forEach(method -> { + String descriptor = getDescriptor(method, m -> null); + if (method.hasAnnotation(PanacheEntityEnhancer.DOTNAME_GENERATE_BRIDGE)) { + toGenerate.put(method.name() + descriptor, method); + toElide.put(method.name() + descriptor, method); + } + }); + generator = new KotlinGenerator(this.cv, typeParameters, typeArguments, types); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, + String[] exceptions) { + MethodInfo methodInfo = toGenerate.get(name + descriptor); + if (methodInfo == null) { + if (toElide.get(name + descriptor) == null) { + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + } else { + if (Modifier.isAbstract(daoClassInfo.flags()) + && methodInfo.hasAnnotation(PanacheEntityEnhancer.DOTNAME_GENERATE_BRIDGE)) { + userMethods.add(name + "/" + descriptor); + } + } + + return null; + } + + @Override + protected void generateModelBridge(MethodInfo method, AnnotationValue targetReturnTypeErased) { + generator.generate(method); + } + + @Override + protected void generateJvmBridge(MethodInfo method) { + if (!Modifier.isAbstract(daoClassInfo.flags())) { + super.generateJvmBridge(method); + } + } +} diff --git a/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/visitors/PanacheMongoCompanionClassVisitor.java b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/visitors/PanacheMongoCompanionClassVisitor.java new file mode 100644 index 0000000000000..338476bb56d3a --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/deployment/src/main/java/io/quarkus/mongodb/panache/kotlin/deployment/visitors/PanacheMongoCompanionClassVisitor.java @@ -0,0 +1,94 @@ +package io.quarkus.mongodb.panache.kotlin.deployment.visitors; + +import static io.quarkus.gizmo.Gizmo.ASM_API_VERSION; +import static org.jboss.jandex.DotName.createSimple; + +import java.util.HashMap; +import java.util.Map; +import java.util.StringJoiner; +import java.util.TreeMap; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +import io.quarkus.deployment.util.AsmUtil; +import io.quarkus.mongodb.panache.deployment.ByteCodeType; +import io.quarkus.mongodb.panache.deployment.TypeBundle; +import io.quarkus.mongodb.panache.kotlin.deployment.KotlinGenerator; +import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; + +public class PanacheMongoCompanionClassVisitor extends ClassVisitor { + private final Map bridgeMethods = new TreeMap<>(); + private final IndexView indexView; + private String entityBinaryType; + private String entitySignature; + private KotlinGenerator generator; + private TypeBundle types; + + public PanacheMongoCompanionClassVisitor(ClassVisitor outputClassVisitor, ClassInfo entityInfo, IndexView indexView, + TypeBundle types) { + super(ASM_API_VERSION, outputClassVisitor); + this.indexView = indexView; + this.types = types; + + entityInfo + .methods() + .forEach(method -> { + if (method.hasAnnotation(PanacheEntityEnhancer.DOTNAME_GENERATE_BRIDGE)) { + bridgeMethods.put(method.name() + AsmUtil.getDescriptor(method, m -> null), method); + } + }); + } + + @Override + public String toString() { + return new StringJoiner(", ", PanacheMongoCompanionClassVisitor.class.getSimpleName() + "[", "]") + .add(entityBinaryType) + .toString(); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + + String className = name.replace('.', '/'); + + ByteCodeType[] foundTypeArguments = KotlinGenerator.recursivelyFindEntityTypeArguments(indexView, + createSimple(name.replace('/', '.')), types.entityCompanionBase().dotName()); + + entityBinaryType = className.replace("$Companion", ""); + entitySignature = "L" + entityBinaryType + ";"; + + Map typeArguments = new HashMap<>(); + typeArguments.put("Entity", entitySignature); + typeArguments.put("Id", foundTypeArguments[1].descriptor()); + + Map typeParameters = new HashMap<>(); + typeParameters.put("Entity", foundTypeArguments[0]); + typeParameters.put("Id", foundTypeArguments[1]); + + generator = new KotlinGenerator(this.cv, typeParameters, typeArguments, types); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodInfo methodInfo = bridgeMethods.get(name + descriptor); + if (methodInfo == null) { + return super.visitMethod(access, name, descriptor, signature, exceptions); + } else { + generator.add(methodInfo); + } + return null; + } + + @Override + public void visitEnd() { + generator.generate(); + if (cv != null) { + cv.visitEnd(); + } + } +} diff --git a/extensions/panache/mongodb-panache-kotlin/pom.xml b/extensions/panache/mongodb-panache-kotlin/pom.xml new file mode 100644 index 0000000000000..468ddbfb7de9d --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/pom.xml @@ -0,0 +1,20 @@ + + + + quarkus-build-parent + io.quarkus + 999-SNAPSHOT + ../../../build-parent/pom.xml + + 4.0.0 + + quarkus-mongodb-panache-kotlin-parent + Quarkus - MongoDB with Panache - Kotlin Parent + pom + + runtime + deployment + + \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/pom.xml b/extensions/panache/mongodb-panache-kotlin/runtime/pom.xml new file mode 100644 index 0000000000000..3002360e4ac20 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/pom.xml @@ -0,0 +1,203 @@ + + + + quarkus-mongodb-panache-kotlin-parent + io.quarkus + 999-SNAPSHOT + ../ + + 4.0.0 + + quarkus-mongodb-panache-kotlin + Quarkus - MongoDB with Panache - Kotlin Runtime + Simplify your persistence code for MongoDB via the active record or the repository pattern + + + io.quarkus + quarkus-core + + + io.quarkus + quarkus-panache-common + + + io.quarkus + quarkus-mongodb-panache-common + + + + + io.quarkus + quarkus-jsonb + true + + + + + io.quarkus + quarkus-jackson + true + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + org.junit.jupiter + junit-jupiter + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin.version} + test + + + org.ow2.asm + asm + test + + + io.quarkus + quarkus-mongodb-panache + test + + + io.quarkus + quarkus-mongodb-panache-common-deployment + test + + + io.quarkus.gizmo + gizmo + test + + + + + + jcenter + JCenter + https://jcenter.bintray.com/ + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + + + + org.jboss.jandex + jandex-maven-plugin + + + make-index + + jandex + + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + src/main/java + src/main/kotlin + + + + + test-compile + test-compile + + test-compile + + + + + 1.8 + + + + org.apache.maven.plugins + maven-compiler-plugin + + -proc:none + + + + + default-compile + none + + + + default-testCompile + none + + + java-compile + compile + + compile + + + + java-test-compile + test-compile + testCompile + + + + + org.jetbrains.dokka + dokka-maven-plugin + 0.10.1 + + + javadoc + pre-site + + dokka + + + + + html + + src/main/java + src/main/kotlin + + + + + https://docs.oracle.com/javase/8/docs/api/ + https://docs.oracle.com/javase/8/docs/api/package-list + + + + + + + diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/java/io/quarkus/mongodb/panache/kotlin/reactive/runtime/ReactivePanacheQueryImpl.java b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/java/io/quarkus/mongodb/panache/kotlin/reactive/runtime/ReactivePanacheQueryImpl.java new file mode 100644 index 0000000000000..c68131430b7c7 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/java/io/quarkus/mongodb/panache/kotlin/reactive/runtime/ReactivePanacheQueryImpl.java @@ -0,0 +1,126 @@ +package io.quarkus.mongodb.panache.kotlin.reactive.runtime; + +import java.util.List; + +import org.bson.conversions.Bson; + +import com.mongodb.client.model.Collation; + +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheQuery; +import io.quarkus.mongodb.panache.reactive.runtime.CommonReactivePanacheQueryImpl; +import io.quarkus.mongodb.reactive.ReactiveMongoCollection; +import io.quarkus.panache.common.Page; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +@SuppressWarnings("unchecked") +public class ReactivePanacheQueryImpl implements ReactivePanacheQuery { + private final CommonReactivePanacheQueryImpl delegate; + + ReactivePanacheQueryImpl(ReactiveMongoCollection collection, Bson mongoQuery, Bson sort) { + this.delegate = new CommonReactivePanacheQueryImpl<>(collection, mongoQuery, sort); + } + + private ReactivePanacheQueryImpl(CommonReactivePanacheQueryImpl delegate) { + this.delegate = delegate; + } + + @Override + public ReactivePanacheQuery project(Class type) { + return new ReactivePanacheQueryImpl<>(delegate.project(type)); + } + + @Override + @SuppressWarnings("unchecked") + public ReactivePanacheQuery page(Page page) { + delegate.page(page); + return this; + } + + @Override + public ReactivePanacheQuery page(int pageIndex, int pageSize) { + return page(Page.of(pageIndex, pageSize)); + } + + @Override + public ReactivePanacheQuery nextPage() { + delegate.nextPage(); + return this; + } + + @Override + public ReactivePanacheQuery previousPage() { + delegate.previousPage(); + return this; + } + + @Override + public ReactivePanacheQuery firstPage() { + delegate.firstPage(); + return this; + } + + @Override + public Uni> lastPage() { + Uni> uni = delegate.lastPage(); + return uni.map(q -> this); + } + + @Override + public Uni hasNextPage() { + return delegate.hasNextPage(); + } + + @Override + public boolean hasPreviousPage() { + return delegate.hasPreviousPage(); + } + + @Override + public Uni pageCount() { + return delegate.pageCount(); + } + + @Override + public Page page() { + return delegate.page(); + } + + @Override + public ReactivePanacheQuery range(int startIndex, int lastIndex) { + delegate.range(startIndex, lastIndex); + return this; + } + + @Override + public ReactivePanacheQuery withCollation(Collation collation) { + delegate.withCollation(collation); + return this; + } + + @Override + public Uni count() { + return delegate.count(); + } + + @Override + public Uni> list() { + return delegate.list(); + } + + @Override + public Multi stream() { + return delegate.stream(); + } + + @Override + public Uni firstResult() { + return delegate.firstResult(); + } + + @Override + @SuppressWarnings("unchecked") + public Uni singleResult() { + return delegate.singleResult(); + } +} diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/java/io/quarkus/mongodb/panache/kotlin/runtime/PanacheQueryImpl.java b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/java/io/quarkus/mongodb/panache/kotlin/runtime/PanacheQueryImpl.java new file mode 100644 index 0000000000000..a1d326f9816bc --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/java/io/quarkus/mongodb/panache/kotlin/runtime/PanacheQueryImpl.java @@ -0,0 +1,125 @@ +package io.quarkus.mongodb.panache.kotlin.runtime; + +import java.util.List; +import java.util.stream.Stream; + +import org.bson.conversions.Bson; + +import com.mongodb.client.MongoCollection; +import com.mongodb.client.model.Collation; + +import io.quarkus.mongodb.panache.kotlin.PanacheQuery; +import io.quarkus.mongodb.panache.runtime.CommonPanacheQueryImpl; +import io.quarkus.panache.common.Page; + +public class PanacheQueryImpl implements PanacheQuery { + private final CommonPanacheQueryImpl delegate; + + PanacheQueryImpl(MongoCollection collection, Bson mongoQuery, Bson sort) { + this.delegate = new CommonPanacheQueryImpl<>(collection, mongoQuery, sort); + } + + private PanacheQueryImpl(CommonPanacheQueryImpl delegate) { + this.delegate = delegate; + } + + @Override + public PanacheQuery project(Class type) { + return new PanacheQueryImpl<>(delegate.project(type)); + } + + @Override + public PanacheQuery page(Page page) { + delegate.page(page); + return this; + } + + @Override + public PanacheQuery page(int pageIndex, int pageSize) { + return page(Page.of(pageIndex, pageSize)); + } + + @Override + public PanacheQuery nextPage() { + delegate.nextPage(); + return this; + } + + @Override + public PanacheQuery previousPage() { + delegate.previousPage(); + return this; + } + + @Override + public PanacheQuery firstPage() { + delegate.firstPage(); + return this; + } + + @Override + public PanacheQuery lastPage() { + delegate.lastPage(); + return this; + } + + @Override + public boolean hasNextPage() { + return delegate.hasNextPage(); + } + + @Override + public boolean hasPreviousPage() { + return delegate.hasPreviousPage(); + } + + @Override + public int pageCount() { + return delegate.pageCount(); + } + + @Override + public Page page() { + return delegate.page(); + } + + @Override + public PanacheQuery range(int startIndex, int lastIndex) { + delegate.range(startIndex, lastIndex); + return this; + } + + @Override + public PanacheQuery withCollation(Collation collation) { + delegate.withCollation(collation); + return this; + } + + // Results + + @Override + public long count() { + return delegate.count(); + } + + @Override + public List list() { + return delegate.list(); + } + + @Override + public Stream stream() { + return delegate.stream(); + } + + @Override + public Entity firstResult() { + return delegate.firstResult(); + } + + @Override + public Entity singleResult() { + return delegate.singleResult(); + } + +} diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoCompanion.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoCompanion.kt new file mode 100644 index 0000000000000..ed893fd7cc9d5 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoCompanion.kt @@ -0,0 +1,702 @@ +package io.quarkus.mongodb.panache.kotlin + +import com.mongodb.client.MongoCollection +import com.mongodb.client.MongoDatabase +import io.quarkus.mongodb.panache.kotlin.PanacheMongoEntityBase.Companion.operations +import io.quarkus.mongodb.panache.PanacheUpdate +import io.quarkus.panache.common.Parameters +import io.quarkus.panache.common.Sort +import io.quarkus.panache.common.impl.GenerateBridge +import org.bson.Document +import org.bson.types.ObjectId +import java.util.stream.Stream + +/** + * Define persistence and query methods for an Entity with a default ID type of [ObjectId] + * + * @param Entity the entity type + */ +interface PanacheMongoCompanion: PanacheMongoCompanionBase + +/** + * Define persistence and query methods for an Entity with a type of Id + * + * @param Entity the entity type + * @param Id the ID type + */ +interface PanacheMongoCompanionBase { + /** + * 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(targetReturnTypeErased = true) + fun findById(id: Id): Entity? = throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, vararg params: Any?): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options with optional indexed parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, sort: Sort, vararg params: Any?): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, params: Map): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options, with named parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, sort: Sort, params: Map): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with named parameters. + * + * @param query a query string + * @param params Parameters of named parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, params: Parameters): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options with named parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params Parameters of indexed parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, sort: Sort, params: Parameters): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * + * @param query a query string + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: Document): PanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities using a a BSON query and a BSON sort. + * + * @param query a query string + * @param sort the sort strategy to use + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: Document, sort: Document): PanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * + * @return a new [PanacheQuery] instance to find all entities of this type. + * @see [findAll] + * @see [listAll] + * @see [streamAll] + */ + @GenerateBridge + fun findAll(): PanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type, in the given order. + * + * @param sort the sort order to use + * @return a new [PanacheQuery] instance to find all entities of this type. + * @see [findAll] + * @see [listAll] + * @see [streamAll] + */ + @GenerateBridge + fun findAll(sort: Sort): PanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, vararg params: Any?): List = throw operations.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 query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, sort: Sort, vararg params: Any?): List = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, params: Map): List = throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, sort: Sort, params: Map): List = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params Parameters of named parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, params: Parameters): List = throw operations.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 query string + * @param sort the sort strategy to use + * @param params Parameters of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, sort: Sort, params: Parameters): List = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * This method is a shortcut for `find(query).list()`. + * + * @param query a query document + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: Document): List = throw operations.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 query document + * @param sort the sort document + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: Document, sort: Document): List = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * This method is a shortcut for `findAll().list()`. + * + * @return a [List] containing all results, without paging + * @see [listAll] + * @see [findAll] + * @see [streamAll] + */ + @GenerateBridge + fun listAll(): List = throw operations.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 [List] containing all results, without paging + * @see [listAll] + * @see [findAll] + * @see [streamAll] + */ + @GenerateBridge + fun listAll(sort: Sort): List = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a Stream containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, vararg params: Any?): Stream = throw operations.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 query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a Stream containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, sort: Sort, vararg params: Any?): Stream = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a Stream containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, params: Map): Stream = + throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a Stream containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, sort: Sort, params: Map): Stream = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params Parameters of named parameters + * @return a Stream containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, params: Parameters): Stream = throw operations.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 query string + * @param sort the sort strategy to use + * @param params Parameters of indexed parameters + * @return a Stream containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, sort: Sort, params: Parameters): Stream = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * This method is a shortcut for `find(query).stream()`. + * + * @param query a query Document + * @return a Stream containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: Document): Stream = throw operations.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 query Document + * @param sort the sort strategy to use + * @return a Stream containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: Document, sort: Document): Stream = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * This method is a shortcut for `findAll().stream()`. + * + * @return a Stream containing all results, without paging + * @see [streamAll] + * @see [findAll] + * @see [listAll] + */ + @GenerateBridge + fun streamAll(): Stream = throw operations.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 Stream containing all results, without paging + * @see [streamAll] + * @see [findAll] + * @see [listAll] + */ + @GenerateBridge + fun streamAll(sort: Sort): Stream = throw operations.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] + */ + @GenerateBridge + fun count(): Long = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return the number of entities counted. + * @see [count] + */ + @GenerateBridge + fun count(query: String, vararg params: Any?): Long = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return the number of entities counted. + * @see [count] + */ + @GenerateBridge + fun count(query: String, params: Map): Long = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query with named parameters. + * + * @param query a query string + * @param params Parameters of named parameters + * @return the number of entities counted. + * @see [count] + */ + @GenerateBridge + fun count(query: String, params: Parameters): Long = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query + * + * @param query a query document + * @return the number of entities counted. + * @see [count] + */ + @GenerateBridge + fun count(query: Document): Long = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type from the database. + * + * @return the number of entities deleted. + * @see [delete] + */ + @GenerateBridge + fun deleteAll(): Long = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return the number of entities deleted. + * @see [deleteAll] + * @see [delete] + */ + @GenerateBridge + fun delete(query: String, vararg params: Any?): Long = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return the number of entities deleted. + * @see [deleteAll] + * @see [delete] + */ + @GenerateBridge + fun delete(query: String, params: Map): Long = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a query string + * @param params Parameters of named parameters + * @return the number of entities deleted. + * @see [deleteAll] + * @see [delete] + */ + @GenerateBridge + fun delete(query: String, params: Parameters): Long = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query + * + * @param query a query document + * @return the number of entities deleted. + * @see [deleteAll] + * @see [delete] + */ + @GenerateBridge + fun delete(query: Document): Long = throw operations.implementationInjectionMissing() + + /** + * Delete an entity of this type by ID. + * + * @param id the ID of the entity to delete. + * @return false if the entity was not deleted (not found). + */ + @GenerateBridge + fun deleteById(id: Id): Boolean = throw operations.implementationInjectionMissing() + + /** + * Insert all given entities. + * + * @param entities the entities to insert + * @see [persist] + */ + fun persist(entities: Iterable) = operations.persist(entities) + + /** + * Insert all given entities. + * + * @param entities the entities to insert + * @see [persist] + */ + fun persist(entities: Stream) = operations.persist(entities) + + /** + * Insert all given entities. + * + * @param entities the entities to insert + * @see [persist] + */ + fun persist(firstEntity: Entity, vararg entities: Entity) = operations.persist(firstEntity, *entities) + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see [update] + */ + fun update(entities: Iterable) = operations.update(entities) + + /** + * Update all given entities. + * + * @param entities the entities to insert + * @see [update] + */ + fun update(entities: Stream) = operations.update(entities) + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see [update] + */ + fun update(firstEntity: Entity, vararg entities: Entity) = operations.update(firstEntity, *entities) + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see [persistOrUpdate] + */ + fun persistOrUpdate(entities: Iterable) = operations.persistOrUpdate(entities) + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see [persistOrUpdate] + */ + fun persistOrUpdate(entities: Stream) = operations.persistOrUpdate(entities) + + /** + * Persist all given entities. + * + * @param entities the entities to update + * @see [persistOrUpdate] + */ + fun persistOrUpdate(firstEntity: Entity, vararg entities: Entity) = operations.persistOrUpdate(firstEntity, *entities) + + /** + * Update all entities of this type using the given update document with optional indexed parameters. + * The returned [PanacheUpdate] object will allow to restrict on which document the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. It can also be expressed as a query string. + * @param params optional sequence of indexed parameters + * @return a new [PanacheUpdate] instance for the given update document + * @see [update] + */ + @GenerateBridge + fun update(update: String, vararg params: Any?): PanacheUpdate = throw operations.implementationInjectionMissing() + + /** + * Update all entities of this type by the given update document with named parameters. + * The returned [PanacheUpdate] object will allow to restrict on which document the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. + * It can also be expressed as a query string. + * + * @param params map of named parameters + * @return a new [PanacheUpdate] instance for the given update document + * @see [update] + */ + @GenerateBridge + fun update(update: String, params: Map): PanacheUpdate = + throw operations.implementationInjectionMissing() + + /** + * Update all entities of this type by the given update document, with named parameters. + * The returned [PanacheUpdate] object will allow to restrict on which document the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. It can also be expressed as a query + * string. + * + * @param params [Parameters] of named parameters + * @return a new [PanacheUpdate] instance for the given update document + * @see [update] + */ + @GenerateBridge + fun update(update: String, params: Parameters): PanacheUpdate = throw operations.implementationInjectionMissing() + + /** + * Allow to access the underlying Mongo Collection. + * + * @return the [MongoCollection] used by this entity + */ + @GenerateBridge + fun mongoCollection(): MongoCollection = throw operations.implementationInjectionMissing() + + /** + * Allow to access the underlying Mongo Database. + * + * @return the [MongoDatabase] used by this entity + */ + @GenerateBridge + fun mongoDatabase(): MongoDatabase = throw operations.implementationInjectionMissing() +} diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoEntity.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoEntity.kt new file mode 100755 index 0000000000000..3207db347a654 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoEntity.kt @@ -0,0 +1,31 @@ +package io.quarkus.mongodb.panache.kotlin + +import org.bson.types.ObjectId + +/** + * Represents an entity with a generated ID field [id] of type [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 [PanacheMongoEntityBase]. + * + * If you want a custom ID type or strategy, you can directly extend [PanacheMongoEntityBase] + * instead, and write your own ID field. You will still get auto-generated accessors and + * all the useful methods. + * + * @see PanacheMongoEntityBase + */ +abstract class PanacheMongoEntity : PanacheMongoEntityBase() { + /** + * The auto-generated ID field. + * This field is set by Mongo when this entity is persisted. + * + * @see [PanacheMongoEntityBase.persist] + */ + var id: ObjectId? = null + + /** + * Default toString() implementation + * + * @return the class type and ID type + */ + override fun toString(): String = "${this.javaClass.simpleName}<$id>" +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoEntityBase.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoEntityBase.kt new file mode 100755 index 0000000000000..3935b0662b569 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoEntityBase.kt @@ -0,0 +1,62 @@ +package io.quarkus.mongodb.panache.kotlin + +import io.quarkus.mongodb.panache.kotlin.runtime.KotlinMongoOperations + +/** + * 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 [PanacheMongoEntity] instead. + * + * @see PanacheMongoEntity + */ +@Suppress("unused") +abstract class PanacheMongoEntityBase { + /** + * Defines internal implementation details for use by quarkus. Use of these members is highly discouraged as the + * implementation may change without warning. + */ + companion object { + /** + * Provides the default implementations for quarkus to wire up. Should not be used by third party developers. + */ + @JvmStatic + val operations = KotlinMongoOperations() + } + + /** + * Persist this entity in the database. This will set its ID field if not already set. + * + * @see [persist] + */ + fun persist() { + operations.persist(this) + } + + /** + * Update this entity in the database. + * + * @see [update] + */ + fun update() { + operations.update(this) + } + + /** + * Persist this entity in the database or update it if it already exists. + * + * @see [persistOrUpdate] + */ + fun persistOrUpdate() { + operations.persistOrUpdate(this) + } + + /** + * Delete this entity from the database if it is already persisted. + * + * @see [delete] + * @see [PanacheMongoCompanionBase.deleteAll] + */ + fun delete() { + operations.delete(this) + } +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoRepository.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoRepository.kt new file mode 100755 index 0000000000000..f4e044fc1fd9c --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoRepository.kt @@ -0,0 +1,13 @@ +package io.quarkus.mongodb.panache.kotlin + +import org.bson.types.ObjectId + +/** + * Represents a Repository for a specific type of entity `Entity`, with an ID type + * of `ObjectId`. Implementing this repository will gain you the exact same useful methods + * that are on [PanacheMongoEntityBase]. If you have a custom ID strategy, you should + * implement [PanacheMongoRepositoryBase] instead. + * + * @param Entity The type of entity to operate on + */ +interface PanacheMongoRepository : PanacheMongoRepositoryBase \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoRepositoryBase.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoRepositoryBase.kt new file mode 100755 index 0000000000000..293355f253372 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheMongoRepositoryBase.kt @@ -0,0 +1,728 @@ +@file:Suppress("unused") + +package io.quarkus.mongodb.panache.kotlin + +import com.mongodb.client.MongoCollection +import com.mongodb.client.MongoDatabase +import io.quarkus.mongodb.panache.PanacheUpdate +import io.quarkus.mongodb.panache.kotlin.PanacheMongoEntityBase.Companion.operations +import io.quarkus.panache.common.Parameters +import io.quarkus.panache.common.Sort +import io.quarkus.panache.common.impl.GenerateBridge +import org.bson.Document +import java.util.stream.Stream + +/** + * Represents a Repository for a specific type of entity `Entity`, with an ID type + * of `Id`. Implementing this repository will gain you the exact same useful methods + * that are on [PanacheMongoEntityBase]. Unless you have a custom ID strategy, you should not + * implement this interface directly but implement [PanacheMongoRepository] instead. + * + * @param Entity The type of entity to operate on + * @param Id The ID type of the entity + * @see PanacheMongoRepository + */ +interface PanacheMongoRepositoryBase { + /** + * 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] + */ + fun persist(entity: Entity) = operations.persist(entity) + + /** + * Update the given entity in the database. + * + * @param entity the entity to update. + * @see [update] + */ + fun update(entity: Entity) = operations.update(entity) + + /** + * Persist the given entity in the database or update it if it already exist. + * + * @param entity the entity to update. + * @see [persistOrUpdate] + */ + fun persistOrUpdate(entity: Entity) = operations.persistOrUpdate(entity) + + /** + * Delete the given entity from the database, if it is already persisted. + * + * @param entity the entity to delete. + * @see [delete] + * @see [deleteAll] + */ + fun delete(entity: Entity) = operations.delete(entity) + + /** + * 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(targetReturnTypeErased = true) + fun findById(id: Id): Entity? = throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, vararg params: Any?): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options, with optional indexed parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, sort: Sort, vararg params: Any?): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, params: Map): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options, with named parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, sort: Sort, params: Map): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with named parameters. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, params: Parameters): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options, with named parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params [Parameters] of indexed parameters + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, sort: Sort, params: Parameters): PanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * + * @param query a [Document] query + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: Document): PanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities using a a BSON query and a BSON sort. + * + * @param query a [Document] query + * @param sort the [Document] sort + * @return a new [PanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: Document, sort: Document): PanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * + * @return a new [PanacheQuery] instance to find all entities of this type. + * @see [findAll] + * @see [listAll] + * @see [streamAll] + */ + @GenerateBridge + fun findAll(): PanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type, in the given order. + * + * @param sort the sort order to use + * @return a new [PanacheQuery] instance to find all entities of this type. + * @see [findAll] + * @see [listAll] + * @see [streamAll] + */ + @GenerateBridge + fun findAll(sort: Sort): PanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, vararg params: Any?): List = throw operations.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 query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, sort: Sort, vararg params: Any?): List = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, params: Map): List = throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, sort: Sort, params: Map): List = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, params: Parameters): List = throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Parameters] of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, sort: Sort, params: Parameters): List = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * This method is a shortcut for `find(query).list()`. + * + * @param query a [Document] query + * @return a [List] containing all results, without paging + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun list(query: Document): List = throw operations.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 [Document] query + * @param sort the [Document] sort + * @return a [List] containing all results, without paging + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun list(query: Document, sort: Document): List = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * This method is a shortcut for `findAll().list()`. + * + * @return a [List] containing all results, without paging + * @see [listAll] + * @see [findAll] + * @see [streamAll] + */ + @GenerateBridge + fun listAll(): List = throw operations.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 [List] containing all results, without paging + * @see [listAll] + * @see [findAll] + * @see [streamAll] + */ + @GenerateBridge + fun listAll(sort: Sort): List = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a [Stream] containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, vararg params: Any?): Stream = throw operations.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 query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a [Stream] containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, sort: Sort, vararg params: Any?): Stream = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a [Stream] containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, params: Map): Stream = + throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a [Stream] containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, sort: Sort, params: Map): Stream = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return a [Stream] containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, params: Parameters): Stream = throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Parameters] of indexed parameters + * @return a [Stream] containing all results, without paging + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, sort: Sort, params: Parameters): Stream = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * This method is a shortcut for `find(query).stream()`. + * + * @param query a [Document] query + * @return a [Stream] containing all results, without paging + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun stream(query: Document): Stream = throw operations.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 [Document] query + * @param sort the [Document] sort + * @return a [Stream] containing all results, without paging + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun stream(query: Document, sort: Document): Stream = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * This method is a shortcut for `findAll().stream()`. + * + * @return a [Stream] containing all results, without paging + * @see [streamAll] + * @see [findAll] + * @see [listAll] + */ + @GenerateBridge + fun streamAll(sort: Sort): Stream = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type, in the given order. + * This method is a shortcut for `findAll(sort).stream()`. + * + * @return a [Stream] containing all results, without paging + * @see [streamAll] + * @see [findAll] + * @see [listAll] + */ + @GenerateBridge + fun streamAll(): Stream = throw operations.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] + */ + @GenerateBridge + fun count(): Long = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return the number of entities counted. + * @see [count] + */ + @GenerateBridge + fun count(query: String, vararg params: Any?): Long = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return the number of entities counted. + * @see [count] + */ + @GenerateBridge + fun count(query: String, params: Map): Long = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query, with named parameters. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return the number of entities counted. + * @see [count] + */ + @GenerateBridge + fun count(query: String, params: Parameters): Long = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query + * + * @param query a [Document] query + * @return he number of entities counted. + * @see [count] + */ + @GenerateBridge + fun count(query: Document): Long = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type from the database. + * + * @return the number of entities deleted. + * @see [delete] + */ + @GenerateBridge + fun deleteAll(): Long = throw operations.implementationInjectionMissing() + + /** + * Delete an entity of this type by ID. + * + * @param id the ID of the entity to delete. + * @return false if the entity was not deleted (not found). + */ + @GenerateBridge + fun deleteById(id: Id): Boolean = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return the number of entities deleted. + * @see [deleteAll] + * @see [delete] + */ + @GenerateBridge + fun delete(query: String, vararg params: Any?): Long = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return the number of entities deleted. + * @see [deleteAll] + * @see [delete] + */ + @GenerateBridge + fun delete(query: String, params: Map): Long = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return the number of entities deleted. + * @see [deleteAll] + * @see [delete] + */ + @GenerateBridge + fun delete(query: String, params: Parameters): Long = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query + * + * @param query a [Document] query + * @return he number of entities counted. + * @see [count] + */ + @GenerateBridge + fun delete(query: Document): Long = throw operations.implementationInjectionMissing() + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see [persist] + */ + fun persist(entities: Iterable) = operations.persist(entities) + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see [persist] + */ + fun persist(entities: Stream) = operations.persist(entities) + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see [persist] + */ + fun persist(firstEntity: Entity, vararg entities: Entity) = operations.persist(firstEntity, *entities) + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see [update] + */ + fun update(entities: Iterable) = operations.update(entities) + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see [update] + */ + fun update(entities: Stream) = operations.update(entities) + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see [update] + */ + fun update(firstEntity: Entity, vararg entities: Entity) = operations.update(firstEntity, *entities) + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see [persistOrUpdate] + */ + fun persistOrUpdate(entities: Iterable) = operations.persistOrUpdate(entities) + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see [persistOrUpdate] + */ + fun persistOrUpdate(entities: Stream) = operations.persistOrUpdate(entities) + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see [update] + */ + fun persistOrUpdate(firstEntity: Entity, vararg entities: Entity) = + operations.persistOrUpdate(firstEntity, *entities) + + /** + * Update all entities of this type by the given update document, with optional indexed parameters. + * The returned [PanacheUpdate] object will allow to restrict on which documents the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. + * It can also be expressed as a query string. + * @param params optional sequence of indexed parameters + * @return a new [PanacheUpdate] instance for the given update document + * @see [update] + */ + @GenerateBridge + fun update(update: String, vararg params: Any?): PanacheUpdate = throw operations.implementationInjectionMissing() + + /** + * Update all entities of this type by the given update document, with named parameters. + * The returned [PanacheUpdate] object will allow to restrict on which documents the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. + * It can also be expressed as a query string. + * @param params [Map] of named parameters + * @return a new [PanacheUpdate] instance for the given update document + * @see [update] + */ + @GenerateBridge + fun update(update: String, params: Map): PanacheUpdate = + throw operations.implementationInjectionMissing() + + /** + * Update all entities of this type by the given update document, with named parameters. + * The returned [PanacheUpdate] object will allow to restrict on which document the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. + * It can also be expressed as a query string. + * @param params [Parameters] of named parameters + * @return a new [PanacheUpdate] instance for the given update document + * @see [update] + */ + @GenerateBridge + fun update(update: String, params: Parameters): PanacheUpdate = throw operations.implementationInjectionMissing() + + /** + * Allow to access the underlying Mongo Collection + */ + @GenerateBridge + fun mongoCollection(): MongoCollection = throw operations.implementationInjectionMissing() + + /** + * Allow to access the underlying Mongo Database. + */ + @GenerateBridge + fun mongoDatabase(): MongoDatabase = throw operations.implementationInjectionMissing() +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheQuery.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheQuery.kt new file mode 100644 index 0000000000000..70d74a11e76bc --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/PanacheQuery.kt @@ -0,0 +1,187 @@ +package io.quarkus.mongodb.panache.kotlin + +import com.mongodb.client.model.Collation +import io.quarkus.panache.common.Page +import io.quarkus.panache.common.exception.PanacheQueryException +import java.util.stream.Stream + +/** + * Interface representing an entity query, which abstracts the use of paging, getting the number of results, and + * operating on [List] or [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 Entity The entity type being queried + */ +interface PanacheQuery { + /** + * Defines a projection class: the getters, and the public fields, will be used to restrict which fields should be + * retrieved from the database. + * + * @return a new query with the same state as the previous one (params, page, range, ...). + */ + fun project(type: Class): PanacheQuery + + /** + * Sets the current page. + * + * @param page the new page + * @return this query, modified + * @see .page + * @see .page + */ + fun page(page: Page): PanacheQuery + + /** + * Sets the current page. + * + * @param pageIndex the page index + * @param pageSize the page size + * @return this query, modified + * @see .page + * @see .page + */ + fun page(pageIndex: Int, pageSize: Int): PanacheQuery + + /** + * Sets the current page to the next page + * + * @return this query, modified + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .previousPage + */ + fun nextPage(): PanacheQuery + + /** + * Sets the current page to the previous page (or the first page if there is no previous page) + * + * @return this query, modified + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .nextPage + */ + fun previousPage(): PanacheQuery + + /** + * Sets the current page to the first page + * + * @return this query, modified + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .lastPage + */ + fun firstPage(): PanacheQuery + + /** + * Sets the current page to the last page. This will cause reading of the entity count. + * + * @return this query, modified + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .firstPage + * @see .count + */ + fun lastPage(): PanacheQuery + + /** + * 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 + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .hasPreviousPage + * @see .count + */ + fun hasNextPage(): Boolean + + /** + * Returns true if there is a page to read before the current one. + * + * @return true if there is a previous page to read + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .hasNextPage + */ + fun hasPreviousPage(): Boolean + + /** + * 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. + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + */ + fun pageCount(): Int + + /** + * Returns the current page. + * + * @return the current page + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .page + * @see .page + */ + fun page(): Page + + /** + * Switch the query to use a fixed range (start index - last index) instead of a page. + * As the range is fixed, subsequent pagination of the query is not possible. + * + * @param startIndex the index of the first element, starting at 0 + * @param lastIndex the index of the last element + * @return this query, modified + */ + fun range(startIndex: Int, lastIndex: Int): PanacheQuery + + /** + * Define the collation used for this query. + * + * @param collation the collation to be used for this query. + * @return this query, modified + */ + fun withCollation(collation: Collation): PanacheQuery + // 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. + */ + fun count(): Long + + /** + * Returns the current page of results as a [List]. + * + * @return the current page of results as a [List]. + * @see .stream + * @see .page + * @see .page + */ + fun list(): List + + /** + * Returns the current page of results as a [Stream]. + * + * @return the current page of results as a [Stream]. + * @see .list + * @see .page + * @see .page + */ + fun stream(): Stream + + /** + * Returns the first result of the current page index. This ignores the current page size to fetch + * a single result. + * + * @return the first result of the current page index, or null if there are no results. + * @see .singleResult + */ + fun firstResult(): Entity? + + /** + * Executes this query for the current page and return a single result. + * + * @return the single result + * @throws PanacheQueryException if there is not exactly one result. + * @see .firstResult + */ + fun singleResult(): Entity? +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoCompanion.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoCompanion.kt new file mode 100644 index 0000000000000..86407a663eb52 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoCompanion.kt @@ -0,0 +1,771 @@ +package io.quarkus.mongodb.panache.kotlin.reactive + +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoEntityBase.Companion.operations +import io.quarkus.mongodb.panache.reactive.ReactivePanacheUpdate +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 +import org.bson.Document +import org.bson.types.ObjectId +import java.util.stream.Stream + +/** + * Define persistence and query methods for an Entity with a default ID type of [ObjectId] + * + * @param Entity the entity type + */ +interface ReactivePanacheMongoCompanion + : ReactivePanacheMongoCompanionBase + +/** + * Define persistence and query methods for an Entity with a type of Id + * + * @param Entity the entity type + * @param Id the ID type + */ +interface ReactivePanacheMongoCompanionBase { + /** + * 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 + fun findById(id: Id): Uni = throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see [find] + * @see [find] + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, vararg params: Any?): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options, with optional indexed parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see [find] + * @see [find] + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, sort: Sort, vararg params: Any?): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see [find] + * @see [find] + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, params: Map): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options, with named parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see [find] + * @see [find] + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, sort: Sort, params: Map): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with named parameters. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see [find] + * @see [find] + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, params: Parameters): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options, with named parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params [Parameters] of indexed parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see [find] + * @see [find] + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun find(query: String, sort: Sort, params: Parameters): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * + * @param query a [Document] query + * @return a new [ReactivePanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [list] + * @see [stream] + * @see [stream] + */ + @GenerateBridge + fun find(query: Document): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities using a a BSON query and a BSON sort. + * + * @param query a [Document] query + * @param sort the [Document] sort + * @return a new [ReactivePanacheQuery] instance for the given query + * @see [find] + * @see [list] + * @see [list] + * @see [stream] + * @see [stream] + */ + @GenerateBridge + fun find(query: Document, sort: Document): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * + * @return a new [ReactivePanacheQuery] instance to find all entities of this type. + * @see [findAll] + * @see [listAll] + * @see [streamAll] + */ + @GenerateBridge + fun findAll(): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type, in the given order. + * + * @param sort the sort order to use + * @return a new [ReactivePanacheQuery] instance to find all entities of this type. + * @see [findAll] + * @see [listAll] + * @see [streamAll] + */ + @GenerateBridge + fun findAll(sort: Sort): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [list] + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, vararg params: Any?): Uni> = throw operations.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 query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [list] + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, sort: Sort, vararg params: Any?): Uni> = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [list] + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, params: Map): Uni> = throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [list] + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, sort: Sort, params: Map): Uni> = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [list] + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, params: Parameters): Uni> = throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Parameters] of indexed parameters + * @return a [List] containing all results, without paging + * @see [list] + * @see [list] + * @see [list] + * @see [find] + * @see [stream] + */ + @GenerateBridge + fun list(query: String, sort: Sort, params: Parameters): Uni> = throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * This method is a shortcut for `find(query).list()`. + * + * @param query a [Document] query + * @return a [List] containing all results, without paging + * @see [find] + * @see [find] + * @see [list] + * @see [stream] + * @see [stream] + */ + @GenerateBridge + fun list(query: Document): Uni> = throw operations.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 [Document] query + * @param sort the [Document] sort + * @return a [List] containing all results, without paging + * @see [find] + * @see [find] + * @see [list] + * @see [stream] + * @see [stream] + */ + @GenerateBridge + fun list(query: Document, sort: Document): Uni> = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * This method is a shortcut for `findAll().list()`. + * + * @return a [List] containing all results, without paging + * @see [listAll] + * @see [findAll] + * @see [streamAll] + */ + @GenerateBridge + fun listAll(): Uni> = throw operations.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 [List] containing all results, without paging + * @see [listAll] + * @see [findAll] + * @see [streamAll] + */ + @GenerateBridge + fun listAll(sort: Sort): Uni> = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a [Multi] containing all results, without paging + * @see [stream] + * @see [stream] + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, vararg params: Any?): Multi = throw operations.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 query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a [Multi] containing all results, without paging + * @see [stream] + * @see [stream] + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, sort: Sort, vararg params: Any?): Multi = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a [Multi] containing all results, without paging + * @see [stream] + * @see [stream] + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, params: Map): Multi = throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a [Multi] containing all results, without paging + * @see [stream] + * @see [stream] + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, sort: Sort, params: Map): Multi = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return a [Multi] containing all results, without paging + * @see [stream] + * @see [stream] + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, params: Parameters): Multi = throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Parameters] of indexed parameters + * @return a [Multi] containing all results, without paging + * @see [stream] + * @see [stream] + * @see [stream] + * @see [find] + * @see [list] + */ + @GenerateBridge + fun stream(query: String, sort: Sort, params: Parameters): Multi = throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * This method is a shortcut for `find(query).stream()`. + * + * @param query a [Document] query + * @return a [Multi] containing all results, without paging + * @see [find] + * @see [find] + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun stream(query: Document): Multi = throw operations.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 [Document] query + * @param sort the [Document] sort + * @return a [Multi] containing all results, without paging + * @see [find] + * @see [find] + * @see [find] + * @see [list] + * @see [stream] + */ + @GenerateBridge + fun stream(query: Document, sort: Document): Multi = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * This method is a shortcut for `findAll().stream()`. + * + * @return a [Multi] containing all results, without paging + * @see [streamAll] + * @see [findAll] + * @see [listAll] + */ + @GenerateBridge + fun streamAll(): Multi = throw operations.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 [Multi] containing all results, without paging + * @see [streamAll] + * @see [findAll] + * @see [listAll] + */ + @GenerateBridge + fun streamAll(sort: Sort): Multi = throw operations.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] + * @see [count] + * @see [count] + */ + @GenerateBridge + fun count(): Uni = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return the number of entities counted. + * @see [count] + * @see [count] + * @see [count] + */ + @GenerateBridge + fun count(query: String, vararg params: Any?): Uni = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return the number of entities counted. + * @see [count] + * @see [count] + * @see [count] + */ + @GenerateBridge + fun count(query: String, params: Map): Uni = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query, with named parameters. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return the number of entities counted. + * @see [count] + * @see [count] + * @see [count] + */ + @GenerateBridge + fun count(query: String, params: Parameters): Uni = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query + * + * @param query a [Document] query + * @return he number of entities counted. + * @see [count] + * @see [count] + * @see [count] + */ + @GenerateBridge + fun count(query: Document): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type from the database. + * + * @return the number of entities deleted. + * @see [delete] + * @see [delete] + * @see [delete] + */ + @GenerateBridge + fun deleteAll(): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete an entity of this type by ID. + * + * @param id the ID of the entity to delete. + * @return false if the entity was not deleted (not found). + */ + @GenerateBridge + fun deleteById(id: Id): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return the number of entities deleted. + * @see [deleteAll] + * @see [delete] + * @see [delete] + */ + @GenerateBridge + fun delete(query: String, vararg params: Any?): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return the number of entities deleted. + * @see [deleteAll] + * @see [delete] + * @see [delete] + */ + @GenerateBridge + fun delete(query: String, params: Map): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return the number of entities deleted. + * @see [deleteAll] + * @see [delete] + * @see [delete] + */ + @GenerateBridge + fun delete(query: String, params: Parameters): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query + * + * @param query a [Document] query + * @return he number of entities counted. + * @see [count] + * @see [count] + * @see [count] + */ + @GenerateBridge + fun delete(query: Document): Uni = throw operations.implementationInjectionMissing() + + /** + * Insert all given entities. + * + * @param entities the entities to insert + * @see [persist] + * @see [persist] + * @see [persist] + */ + fun persist(entities: Iterable): Uni = operations.persist(entities) + + /** + * Insert all given entities. + * + * @param entities the entities to insert + * @see [persist] + * @see [persist] + * @see [persist] + */ + fun persist(entities: Stream): Uni = operations.persist(entities) + + /** + * Insert all given entities. + * + * @param entities the entities to update + * @see [persist] + * @see [persist] + * @see [persist] + */ + fun persist(firstEntity: Entity, vararg entities: Entity): Uni = operations.persist(firstEntity, *entities) + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see [update] + * @see [update] + * @see [update] + */ + fun update(entities: Iterable): Uni = operations.update(entities) + + /** + * Update all given entities. + * + * @param entities the entities to insert + * @see [update] + * @see [update] + * @see [update] + */ + fun update(entities: Stream): Uni = operations.update(entities) + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see [update] + * @see [update] + * @see [update] + */ + fun update(firstEntity: Entity, vararg entities: Entity): Uni = operations.update(firstEntity, *entities) + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see [persistOrUpdate] + * @see [persistOrUpdate] + * @see [persistOrUpdate] + */ + fun persistOrUpdate(entities: Iterable): Uni = operations.persistOrUpdate(entities) + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see [persistOrUpdate] + * @see [persistOrUpdate] + * @see [persistOrUpdate] + */ + fun persistOrUpdate(entities: Stream): Uni = operations.persistOrUpdate(entities) + + /** + * Persist all given entities. + * + * @param entities the entities to update + * @see [persistOrUpdate] + * @see [persistOrUpdate] + * @see [persistOrUpdate] + */ + fun persistOrUpdate(firstEntity: Entity, vararg entities: Entity): Uni = + operations.persistOrUpdate(firstEntity, *entities) + + /** + * Update all entities of this type by the given update document, with optional indexed parameters. + * The returned [ReactivePanacheUpdate] object will allow to restrict on which document the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. + * It can also be expressed as a query string. + * @param params optional sequence of indexed parameters + * @return a new [ReactivePanacheUpdate] instance for the given update document + * @see [update] + * @see [update] + */ + @GenerateBridge + fun update(update: String, vararg params: Any?): ReactivePanacheUpdate = throw operations.implementationInjectionMissing() + + /** + * Update all entities of this type by the given update document, with named parameters. + * The returned [ReactivePanacheUpdate] object will allow to restrict on which document the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. + * It can also be expressed as a query string. + * @param params [Map] of named parameters + * @return a new [ReactivePanacheUpdate] instance for the given update document + * @see [update] + */ + @GenerateBridge + fun update(update: String, params: Map): ReactivePanacheUpdate = throw operations.implementationInjectionMissing() + + /** + * Update all entities of this type by the given update document, with named parameters. + * The returned [ReactivePanacheUpdate] object will allow to restrict on which document the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. + * It can also be expressed as a query string. + * @param params [Parameters] of named parameters + * @return a new [ReactivePanacheUpdate] instance for the given update document + * @see [update] + */ + @GenerateBridge + fun update(update: String, params: Parameters): ReactivePanacheUpdate = throw operations.implementationInjectionMissing() + + /** + * Allow to access the underlying Mongo Collection. + */ + @GenerateBridge + fun mongoCollection(): ReactiveMongoCollection = throw operations.implementationInjectionMissing() + + /** + * Allow to access the underlying Mongo Database. + */ + @GenerateBridge + fun mongoDatabase(): ReactiveMongoDatabase = throw operations.implementationInjectionMissing() + +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoEntity.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoEntity.kt new file mode 100644 index 0000000000000..2c886d900a7e7 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoEntity.kt @@ -0,0 +1,31 @@ +package io.quarkus.mongodb.panache.kotlin.reactive + +import org.bson.types.ObjectId + +/** + * Represents an entity with a generated ID field [id] of type [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 [ReactivePanacheMongoEntityBase]. + * + * If you want a custom ID type or strategy, you can directly extend [ReactivePanacheMongoEntityBase] + * instead, and write your own ID field. You will still get auto-generated accessors and + * all the useful methods. + * + * @see ReactivePanacheMongoEntityBase + */ +abstract class ReactivePanacheMongoEntity : ReactivePanacheMongoEntityBase() { + /** + * The auto-generated ID field. + * This field is set by Mongo when this entity is persisted. + * + * @see [persist] + */ + var id: ObjectId? = null + + /** + * Default toString() implementation + * + * @return the class type and ID type + */ + override fun toString(): String = "${javaClass.simpleName}<$id>" +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoEntityBase.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoEntityBase.kt new file mode 100644 index 0000000000000..1fe00ef0556dd --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoEntityBase.kt @@ -0,0 +1,64 @@ +package io.quarkus.mongodb.panache.kotlin.reactive + +import io.quarkus.mongodb.panache.kotlin.reactive.runtime.KotlinReactiveMongoOperations +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 + * [ReactivePanacheMongoEntity] instead. + * + * @see ReactivePanacheMongoEntity + */ +abstract class ReactivePanacheMongoEntityBase { + /** + * Defines internal implementation details for use by quarkus. Use of these members is highly discouraged as the + * implementation may change without warning. + */ + companion object { + /** + * Provides the default implementations for quarkus to wire up. Should not be used by third party developers. + */ + @JvmStatic + val operations = KotlinReactiveMongoOperations() + } + + /** + * Persist this entity in the database. + * This will set it's ID field if not already set. + * + * @see .persist + * @see .persist + * @see .persist + */ + fun persist(): Uni = operations.persist(this) + + /** + * Update this entity in the database. + * + * @see .update + * @see .update + * @see .update + */ + fun update(): Uni = operations.update(this) + + /** + * Persist this entity in the database or update it if it already exist. + * + * @see .persistOrUpdate + * @see .persistOrUpdate + * @see .persistOrUpdate + */ + fun persistOrUpdate(): Uni = operations.persistOrUpdate(this) + + /** + * Delete this entity from the database, if it is already persisted. + * + * @see .delete + * @see .delete + * @see .delete + * @see .deleteAll + */ + fun delete(): Uni = operations.delete(this) +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoRepository.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoRepository.kt new file mode 100644 index 0000000000000..6d02e9db4fea5 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoRepository.kt @@ -0,0 +1,13 @@ +package io.quarkus.mongodb.panache.kotlin.reactive + +import org.bson.types.ObjectId + +/** + * Represents a Repository for a specific type of entity `Entity`, with an ID type + * of `ObjectId`. Implementing this repository will gain you the exact same useful methods + * that are on [ReactivePanacheMongoEntityBase]. If you have a custom ID strategy, you should + * implement [ReactivePanacheMongoRepositoryBase] instead. + * + * @param Entity The type of entity to operate on + */ +interface ReactivePanacheMongoRepository : ReactivePanacheMongoRepositoryBase \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoRepositoryBase.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoRepositoryBase.kt new file mode 100644 index 0000000000000..13aabded7c0dd --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheMongoRepositoryBase.kt @@ -0,0 +1,833 @@ +@file:Suppress("unused") + +package io.quarkus.mongodb.panache.kotlin.reactive + +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoEntityBase.Companion.operations +import io.quarkus.mongodb.panache.reactive.ReactivePanacheUpdate +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 +import org.bson.Document +import java.util.stream.Stream + +/** + * Represents a Repository for a specific type of entity `Entity`, with an ID type + * of `Id`. Implementing this repository will gain you the exact same useful methods + * that are on [ReactivePanacheMongoEntityBase]. Unless you have a custom ID strategy, you should not + * implement this interface directly but implement [ReactivePanacheMongoRepository] instead. + * + * @param Entity The type of entity to operate on + * @param Id The ID type of the entity + * @see ReactivePanacheMongoRepository + */ +interface ReactivePanacheMongoRepositoryBase { + /** + * 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 + * @see .persist + * @see .persist + */ + fun persist(entity: Entity): Uni = operations.persist(entity) + + /** + * Update the given entity in the database. + * + * @param entity the entity to update. + * @see .update + * @see .update + * @see .update + */ + fun update(entity: Entity): Uni = operations.update(entity) + + /** + * Persist the given entity in the database or update it if it already exist. + * + * @param entity the entity to update. + * @see .persistOrUpdate + * @see .persistOrUpdate + * @see .persistOrUpdate + */ + fun persistOrUpdate(entity: Entity): Uni = operations.persistOrUpdate(entity) + + /** + * Delete the given entity from the database, if it is already persisted. + * + * @param entity the entity to delete. + * @see .delete + * @see .delete + * @see .delete + * @see .deleteAll + */ + fun delete(entity: Entity): Uni = operations.delete(entity) + + /** + * 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 + fun findById(id: Id): Uni = throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see .find + * @see .find + * @see .find + * @see .list + * @see .stream + */ + @GenerateBridge + fun find(query: String, vararg params: Any?): ReactivePanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options, with optional indexed parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see .find + * @see .find + * @see .find + * @see .list + * @see .stream + */ + @GenerateBridge + fun find(query: String, sort: Sort, vararg params: Any?): ReactivePanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see .find + * @see .find + * @see .find + * @see .list + * @see .stream + */ + @GenerateBridge + fun find(query: String, params: Map): ReactivePanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options, with named parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see .find + * @see .find + * @see .find + * @see .list + * @see .stream + */ + @GenerateBridge + fun find(query: String, sort: Sort, params: Map): ReactivePanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query, with named parameters. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see .find + * @see .find + * @see .find + * @see .list + * @see .stream + */ + @GenerateBridge + fun find(query: String, params: Parameters): ReactivePanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a query and the given sort options, with named parameters. + * + * @param query a query string + * @param sort the sort strategy to use + * @param params [Parameters] of indexed parameters + * @return a new [ReactivePanacheQuery] instance for the given query + * @see .find + * @see .find + * @see .find + * @see .list + * @see .stream + */ + @GenerateBridge + fun find(query: String, sort: Sort, params: Parameters): ReactivePanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * + * @param query a [Document] query + * @return a new [ReactivePanacheQuery] instance for the given query + * @see .find + * @see .list + * @see .list + * @see .stream + * @see .stream + */ + @GenerateBridge + fun find(query: Document): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities using a a BSON query and a BSON sort. + * + * @param query a [Document] query + * @param sort the [Document] sort + * @return a new [ReactivePanacheQuery] instance for the given query + * @see .find + * @see .list + * @see .list + * @see .stream + * @see .stream + */ + @GenerateBridge + fun find(query: Document, sort: Document): ReactivePanacheQuery = + throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * + * @return a new [ReactivePanacheQuery] instance to find all entities of this type. + * @see .findAll + * @see .listAll + * @see .streamAll + */ + @GenerateBridge + fun findAll(): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type, in the given order. + * + * @param sort the sort order to use + * @return a new [ReactivePanacheQuery] instance to find all entities of this type. + * @see .findAll + * @see .listAll + * @see .streamAll + */ + @GenerateBridge + fun findAll(sort: Sort): ReactivePanacheQuery = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a [List] containing all results, without paging + * @see .list + * @see .list + * @see .list + * @see .find + * @see .stream + */ + @GenerateBridge + fun list(query: String, vararg params: Any?): Uni> = throw operations.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 query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a [List] containing all results, without paging + * @see .list + * @see .list + * @see .list + * @see .find + * @see .stream + */ + @GenerateBridge + fun list(query: String, sort: Sort, vararg params: Any?): Uni> = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a [List] containing all results, without paging + * @see .list + * @see .list + * @see .list + * @see .find + * @see .stream + */ + @GenerateBridge + fun list(query: String, params: Map): Uni> = + throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a [List] containing all results, without paging + * @see .list + * @see .list + * @see .list + * @see .find + * @see .stream + */ + @GenerateBridge + fun list(query: String, sort: Sort, params: Map): Uni> = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).list()`. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return a [List] containing all results, without paging + * @see .list + * @see .list + * @see .list + * @see .find + * @see .stream + */ + @GenerateBridge + fun list(query: String, params: Parameters): Uni> = throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Parameters] of indexed parameters + * @return a [List] containing all results, without paging + * @see .list + * @see .list + * @see .list + * @see .find + * @see .stream + */ + @GenerateBridge + fun list(query: String, sort: Sort, params: Parameters): Uni> = throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * This method is a shortcut for `find(query).list()`. + * + * @param query a [Document] query + * @return a [List] containing all results, without paging + * @see .find + * @see .find + * @see .list + * @see .stream + * @see .stream + */ + @GenerateBridge + fun list(query: Document): Uni> = throw operations.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 [Document] query + * @param sort the [Document] sort + * @return a [List] containing all results, without paging + * @see .find + * @see .find + * @see .list + * @see .stream + * @see .stream + */ + @GenerateBridge + fun list(query: Document, sort: Document): Uni> = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * This method is a shortcut for `findAll().list()`. + * + * @return a [List] containing all results, without paging + * @see .listAll + * @see .findAll + * @see .streamAll + */ + @GenerateBridge + fun listAll(): Uni> = throw operations.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 [List] containing all results, without paging + * @see .listAll + * @see .findAll + * @see .streamAll + */ + @GenerateBridge + fun listAll(sort: Sort): Uni> = throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with optional indexed parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return a [Multi] containing all results, without paging + * @see .stream + * @see .stream + * @see .stream + * @see .find + * @see .list + */ + @GenerateBridge + fun stream(query: String, vararg params: Any?): Multi = throw operations.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 query string + * @param sort the sort strategy to use + * @param params optional sequence of indexed parameters + * @return a [Multi] containing all results, without paging + * @see .stream + * @see .stream + * @see .stream + * @see .find + * @see .list + */ + @GenerateBridge + fun stream(query: String, sort: Sort, vararg params: Any?): Multi = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params [Map] of named parameters + * @return a [Multi] containing all results, without paging + * @see .stream + * @see .stream + * @see .stream + * @see .find + * @see .list + */ + @GenerateBridge + fun stream(query: String, params: Map): Multi = + throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Map] of indexed parameters + * @return a [Multi] containing all results, without paging + * @see .stream + * @see .stream + * @see .stream + * @see .find + * @see .list + */ + @GenerateBridge + fun stream(query: String, sort: Sort, params: Map): Multi = + throw operations.implementationInjectionMissing() + + /** + * Find entities matching a query, with named parameters. + * This method is a shortcut for `find(query, params).stream()`. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return a [Multi] containing all results, without paging + * @see .stream + * @see .stream + * @see .stream + * @see .find + * @see .list + */ + @GenerateBridge + fun stream(query: String, params: Parameters): Multi = throw operations.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 query string + * @param sort the sort strategy to use + * @param params [Parameters] of indexed parameters + * @return a [Multi] containing all results, without paging + * @see .stream + * @see .stream + * @see .stream + * @see .find + * @see .list + */ + @GenerateBridge + fun stream(query: String, sort: Sort, params: Parameters): Multi = + throw operations.implementationInjectionMissing() + + /** + * Find entities using a BSON query. + * This method is a shortcut for `find(query).stream()`. + * + * @param query a [Document] query + * @return a [Multi] containing all results, without paging + * @see .find + * @see .find + * @see .list + * @see .list + * @see .stream + * @see .stream + */ + @GenerateBridge + fun stream(query: Document): Multi = throw operations.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 [Document] query + * @param sort the [Document] sort + * @return a [Multi] containing all results, without paging + * @see .find + * @see .find + * @see .list + * @see .list + * @see .stream + * @see .stream + */ + @GenerateBridge + fun stream(query: Document, sort: Document): Multi = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type. + * This method is a shortcut for `findAll().stream()`. + * + * @return a [Multi] containing all results, without paging + * @see .streamAll + * @see .findAll + * @see .listAll + */ + @GenerateBridge + fun streamAll(sort: Sort): Multi = throw operations.implementationInjectionMissing() + + /** + * Find all entities of this type, in the given order. + * This method is a shortcut for `findAll(sort).stream()`. + * + * @return a [Multi] containing all results, without paging + * @see .streamAll + * @see .findAll + * @see .listAll + */ + @GenerateBridge + fun streamAll(): Multi = throw operations.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 + * @see .count + * @see .count + */ + @GenerateBridge + fun count(): Uni = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return the number of entities counted. + * @see .count + * @see .count + * @see .count + */ + @GenerateBridge + fun count(query: String, vararg params: Any?): Uni = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return the number of entities counted. + * @see .count + * @see .count + * @see .count + */ + @GenerateBridge + fun count(query: String, params: Map): Uni = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query, with named parameters. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return the number of entities counted. + * @see .count + * @see .count + * @see .count + */ + @GenerateBridge + fun count(query: String, params: Parameters): Uni = throw operations.implementationInjectionMissing() + + /** + * Counts the number of this type of entity matching the given query + * + * @param query a [Document] query + * @return he number of entities counted. + * @see .count + * @see .count + * @see .count + */ + @GenerateBridge + fun count(query: Document): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type from the database. + * + * @return the number of entities deleted. + * @see .delete + * @see .delete + * @see .delete + */ + @GenerateBridge + fun deleteAll(): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete an entity of this type by ID. + * + * @param id the ID of the entity to delete. + * @return false if the entity was not deleted (not found). + */ + @GenerateBridge + fun deleteById(id: Id): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with optional indexed parameters. + * + * @param query a query string + * @param params optional sequence of indexed parameters + * @return the number of entities deleted. + * @see .deleteAll + * @see .delete + * @see .delete + */ + @GenerateBridge + fun delete(query: String, vararg params: Any?): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a query string + * @param params [Map] of named parameters + * @return the number of entities deleted. + * @see .deleteAll + * @see .delete + * @see .delete + */ + @GenerateBridge + fun delete(query: String, params: Map): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query, with named parameters. + * + * @param query a query string + * @param params [Parameters] of named parameters + * @return the number of entities deleted. + * @see .deleteAll + * @see .delete + * @see .delete + */ + @GenerateBridge + fun delete(query: String, params: Parameters): Uni = throw operations.implementationInjectionMissing() + + /** + * Delete all entities of this type matching the given query + * + * @param query a [Document] query + * @return he number of entities counted. + * @see .count + * @see .count + * @see .count + */ + @GenerateBridge + fun delete(query: Document): Uni = throw operations.implementationInjectionMissing() + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see .persist + * @see .persist + * @see .persist + */ + fun persist(entities: Iterable): Uni = operations.persist(entities) + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see .persist + * @see .persist + * @see .persist + */ + fun persist(entities: Stream): Uni = operations.persist(entities) + + /** + * Persist all given entities. + * + * @param entities the entities to insert + * @see .persist + * @see .persist + * @see .persist + */ + fun persist(firstEntity: Entity, vararg entities: Entity): Uni = + operations.persist(firstEntity, *entities) + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see .update + * @see .update + * @see .update + */ + fun update(entities: Iterable): Uni = operations.update(entities) + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see .update + * @see .update + * @see .update + */ + fun update(entities: Stream): Uni = operations.update(entities) + + /** + * Update all given entities. + * + * @param entities the entities to update + * @see .update + * @see .update + * @see .update + */ + fun update(firstEntity: Entity, vararg entities: Entity): Uni = + operations.update(firstEntity, *entities) + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see .persistOrUpdate + * @see .persistOrUpdate + * @see .persistOrUpdate + */ + fun persistOrUpdate(entities: Iterable): Uni = + operations.persistOrUpdate(entities) + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see .persistOrUpdate + * @see .persistOrUpdate + * @see .persistOrUpdate + */ + fun persistOrUpdate(entities: Stream): Uni = + operations.persistOrUpdate(entities) + + /** + * Persist all given entities or update them if they already exist. + * + * @param entities the entities to update + * @see .update + * @see .update + * @see .update + */ + fun persistOrUpdate(firstEntity: Entity, vararg entities: Entity): Uni = + operations.persistOrUpdate(firstEntity, *entities) + + /** + * Update all entities of this type by the given update document, with optional indexed parameters. + * The returned [ReactivePanacheUpdate] object will allow to restrict on which document the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. + * It can also be expressed as a query string. + * @param params optional sequence of indexed parameters + * @return a new [ReactivePanacheUpdate] instance for the given update document + * @see .update + * @see .update + */ + @GenerateBridge + fun update(update: String, vararg params: Any?): ReactivePanacheUpdate = + throw operations.implementationInjectionMissing() + + /** + * Update all entities of this type by the given update document, with named parameters. + * The returned [ReactivePanacheUpdate] object will allow to restrict on which document the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. + * It can also be expressed as a query string. + * @param params [Map] of named parameters + * @return a new [ReactivePanacheUpdate] instance for the given update document + * @see .update + * @see .update + */ + @GenerateBridge + fun update(update: String, params: Map): ReactivePanacheUpdate = + throw operations.implementationInjectionMissing() + + /** + * Update all entities of this type by the given update document, with named parameters. + * The returned [ReactivePanacheUpdate] object will allow to restrict on which document the update should be applied. + * + * @param update the update document, if it didn't contain `$set` we add it. + * It can also be expressed as a query string. + * @param params [Parameters] of named parameters + * @return a new [ReactivePanacheUpdate] instance for the given update document + * @see .update + * @see .update + */ + @GenerateBridge + fun update(update: String, params: Parameters): ReactivePanacheUpdate = + throw operations.implementationInjectionMissing() + + /** + * Allow to access the underlying Mongo Collection + */ + @GenerateBridge + fun mongoCollection(): ReactiveMongoCollection = throw operations.implementationInjectionMissing() + + /** + * Allow to access the underlying Mongo Database. + */ + @GenerateBridge + fun mongoDatabase(): ReactiveMongoDatabase = throw operations.implementationInjectionMissing() +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheQuery.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheQuery.kt new file mode 100644 index 0000000000000..737c9d2e855cf --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/ReactivePanacheQuery.kt @@ -0,0 +1,186 @@ +package io.quarkus.mongodb.panache.kotlin.reactive + +import com.mongodb.client.model.Collation +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 [List] or [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 Entity The entity type being queried + */ +interface ReactivePanacheQuery { + /** + * Defines a projection class: the getters, and the public fields, will be used to restrict which fields should be + * retrieved from the database. + * + * @return @return a new query with the same state as the previous one (params, page, range, ...). + */ + fun project(type: Class): ReactivePanacheQuery + + /** + * Sets the current page. + * + * @param page the new page + * @return this query, modified + * @see .page + * @see .page + */ + fun page(page: Page): ReactivePanacheQuery + + /** + * Sets the current page. + * + * @param pageIndex the page index + * @param pageSize the page size + * @return this query, modified + * @see .page + * @see .page + */ + fun page(pageIndex: Int, pageSize: Int): ReactivePanacheQuery + + /** + * Sets the current page to the next page + * + * @return this query, modified + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .previousPage + */ + fun nextPage(): ReactivePanacheQuery + + /** + * Sets the current page to the previous page (or the first page if there is no previous page) + * + * @return this query, modified + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .nextPage + */ + fun previousPage(): ReactivePanacheQuery + + /** + * Sets the current page to the first page + * + * @return this query, modified + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .lastPage + */ + fun firstPage(): ReactivePanacheQuery + + /** + * Sets the current page to the last page. This will cause reading of the entity count. + * + * @return this query, modified + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .firstPage + * @see .count + */ + fun lastPage(): Uni> + + /** + * 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 + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .hasPreviousPage + * @see .count + */ + fun hasNextPage(): Uni + + /** + * Returns true if there is a page to read before the current one. + * + * @return true if there is a previous page to read + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .hasNextPage + */ + fun hasPreviousPage(): Boolean + + /** + * 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. + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + */ + fun pageCount(): Uni + + /** + * Returns the current page. + * + * @return the current page + * @throws UnsupportedOperationException if a page hasn't been set or if a range is already set + * @see .page + * @see .page + */ + fun page(): Page + + /** + * Switch the query to use a fixed range (start index - last index) instead of a page. + * As the range is fixed, subsequent pagination of the query is not possible. + * + * @param startIndex the index of the first element, starting at 0 + * @param lastIndex the index of the last element + * @return this query, modified + */ + fun range(startIndex: Int, lastIndex: Int): ReactivePanacheQuery + + /** + * Define the collation used for this query. + * + * @param collation the collation to be used for this query. + * @return this query, modified + */ + fun withCollation(collation: Collation): ReactivePanacheQuery + + /** + * 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. + */ + fun count(): Uni + + /** + * Returns the current page of results as a [List]. + * + * @return the current page of results as a [List]. + * @see .page + * @see .page + */ + fun list(): Uni> + + /** + * Returns the current page of results as a [Stream]. + * + * @return the current page of results as a [Stream]. + * @see .list + * @see .page + * @see .page + */ + fun stream(): Multi + + /** + * 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 + */ + fun firstResult(): Uni + + /** + * 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 + */ + fun singleResult(): Uni +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/runtime/KotlinReactiveMongoOperations.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/runtime/KotlinReactiveMongoOperations.kt new file mode 100644 index 0000000000000..6bd6d4dceb2d1 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/reactive/runtime/KotlinReactiveMongoOperations.kt @@ -0,0 +1,59 @@ +package io.quarkus.mongodb.panache.kotlin.reactive.runtime + +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheQuery +import io.quarkus.mongodb.panache.reactive.ReactivePanacheUpdate +import io.quarkus.mongodb.panache.reactive.runtime.ReactiveMongoOperations +import io.quarkus.mongodb.panache.reactive.runtime.ReactivePanacheUpdateImpl +import io.quarkus.mongodb.panache.runtime.MongoOperations +import io.quarkus.mongodb.reactive.ReactiveMongoCollection +import io.smallrye.mutiny.Multi +import io.smallrye.mutiny.Uni +import org.bson.Document +import java.util.stream.Stream + +/** + * Defines kotlin specific implementations of methods needed by [ReactiveMongoOperations]. + */ +class KotlinReactiveMongoOperations : ReactiveMongoOperations, ReactivePanacheUpdate>() { + /** + * Creates the query implementation + * + * @param collection the collection to query + * @param query the query to base the new query off of + * @param sortDoc the sort document to use + * + * @return the new query implementation + */ + override fun createQuery(collection: ReactiveMongoCollection<*>, query: Document?, sortDoc: Document?) = + ReactivePanacheQueryImpl(collection, query, sortDoc) + + /** + * Creates the update implementation + * + * @param collection the collection to query + * @param entityClass the type to update + * @param docUpdate the update document to start with + * + * @return the new query implementation + */ + override fun createUpdate(collection: ReactiveMongoCollection<*>, entityClass: Class<*>, docUpdate: Document) = + ReactivePanacheUpdateImpl(this, entityClass, docUpdate, collection) + + /** + * Extracts the query results in to a List. + * + * @param query the query to list + * + * @return the query results + */ + override fun list(query: ReactivePanacheQuery<*>): Uni> = query.list() as Uni> + + /** + * Extracts the query results in to a Stream. + * + * @param query the query to stream + * + * @return the query results + */ + override fun stream(query: ReactivePanacheQuery<*>): Multi<*> = query.stream() +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/runtime/KotlinMongoOperations.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/runtime/KotlinMongoOperations.kt new file mode 100644 index 0000000000000..71c345b34e998 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/main/kotlin/io/quarkus/mongodb/panache/kotlin/runtime/KotlinMongoOperations.kt @@ -0,0 +1,57 @@ +package io.quarkus.mongodb.panache.kotlin.runtime + +import com.mongodb.client.MongoCollection +import io.quarkus.mongodb.panache.PanacheUpdate +import io.quarkus.mongodb.panache.kotlin.PanacheQuery +import io.quarkus.mongodb.panache.runtime.MongoOperations +import io.quarkus.mongodb.panache.runtime.PanacheUpdateImpl +import org.bson.Document +import java.util.stream.Stream + +/** + * Defines kotlin specific implementations of methods needed by [MongoOperations]. + */ +class KotlinMongoOperations : MongoOperations, PanacheUpdate>() { + + /** + * Creates the query implementation + * + * @param collection the collection to query + * @param query the query to base the new query off of + * @param sortDoc the sort document to use + * + * @return the new query implementation + */ + override fun createQuery(collection: MongoCollection<*>, query: Document?, sortDoc: Document?) = + PanacheQueryImpl(collection, query, sortDoc) + + /** + * Creates the update implementation + * + * @param collection the collection to query + * @param entityClass the type to update + * @param docUpdate the update document to start with + * + * @return the new query implementation + */ + override fun createUpdate(collection: MongoCollection<*>, entityClass: Class<*>, docUpdate: Document) = + PanacheUpdateImpl(this, entityClass, docUpdate, collection) + + /** + * Extracts the query results in to a List. + * + * @param query the query to list + * + * @return a [List] of the results + */ + override fun list(query: PanacheQuery<*>): List<*> = query.list() + + /** + * Extracts the query results in to a Stream. + * + * @param query the query to stream + * + * @return a [Stream] of the results + */ + override fun stream(query: PanacheQuery<*>): Stream<*> = query.stream() +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/src/test/kotlin/io/quarkus/mongodb/panache/kotlin/TestAnalogs.kt b/extensions/panache/mongodb-panache-kotlin/runtime/src/test/kotlin/io/quarkus/mongodb/panache/kotlin/TestAnalogs.kt new file mode 100644 index 0000000000000..abc3c44cf15f6 --- /dev/null +++ b/extensions/panache/mongodb-panache-kotlin/runtime/src/test/kotlin/io/quarkus/mongodb/panache/kotlin/TestAnalogs.kt @@ -0,0 +1,224 @@ +package io.quarkus.mongodb.panache.kotlin + +import io.quarkus.gizmo.Gizmo +import io.quarkus.mongodb.panache.deployment.ByteCodeType +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoCompanionBase +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoEntityBase +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepository +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepositoryBase +import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.objectweb.asm.ClassReader +import org.objectweb.asm.ClassReader.SKIP_CODE +import org.objectweb.asm.ClassVisitor +import org.objectweb.asm.MethodVisitor +import org.objectweb.asm.Opcodes +import org.objectweb.asm.Type +import org.objectweb.asm.Type.getArgumentTypes +import org.objectweb.asm.Type.getReturnType +import org.objectweb.asm.Type.getType +import java.util.Optional +import java.util.function.Consumer +import kotlin.reflect.KClass +import io.quarkus.mongodb.panache.PanacheMongoEntity as JavaPanacheMongoEntity +import io.quarkus.mongodb.panache.PanacheMongoEntityBase as JavaPanacheMongoEntityBase +import io.quarkus.mongodb.panache.PanacheMongoRepository as JavaPanacheMongoRepository +import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase as JavaPanacheMongoRepositoryBase +import io.quarkus.mongodb.panache.PanacheQuery as JavaPanacheQuery + +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntity as ReactiveJavaPanacheMongoEntity +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntityBase as ReactiveJavaPanacheMongoEntityBase +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepository as ReactiveJavaPanacheMongoRepository +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepositoryBase as ReactiveJavaPanacheMongoRepositoryBase +import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery as ReactiveJavaPanacheQuery + +class TestAnalogs { + @Test + fun testPanacheQuery() { + compare(map(JavaPanacheQuery::class), map(PanacheQuery::class)) + compare(map(ReactiveJavaPanacheQuery::class), map(ReactivePanacheQuery::class)) + } + + @Test + fun testPanacheRepository() { + compare(map(JavaPanacheMongoRepository::class), map(PanacheMongoRepository::class)) + compare(map(ReactiveJavaPanacheMongoRepository::class), map(ReactivePanacheMongoRepository::class)) + } + + @Test + fun testPanacheRepositoryBase() { + compare(map(JavaPanacheMongoRepositoryBase::class), map(PanacheMongoRepositoryBase::class), listOf("findByIdOptional")) + compare(map(ReactiveJavaPanacheMongoRepositoryBase::class), map(ReactivePanacheMongoRepositoryBase::class), listOf("findByIdOptional")) + } + + @Test + fun testPanacheEntity() { + compare(JavaPanacheMongoEntity::class, PanacheMongoEntity::class, PanacheMongoCompanion::class) + compare(JavaPanacheMongoEntityBase::class, PanacheMongoEntityBase::class, PanacheMongoCompanionBase::class) + compare(ReactiveJavaPanacheMongoEntity::class, PanacheMongoEntity::class, PanacheMongoCompanion::class) + compare(ReactiveJavaPanacheMongoEntityBase::class, ReactivePanacheMongoEntityBase::class, ReactivePanacheMongoCompanionBase::class) + } + + private fun compare(javaEntity: KClass<*>, + kotlinEntity: KClass<*>, + companion: KClass<*>) { + val javaMethods = map(javaEntity).methods + val kotlinMethods = map(kotlinEntity).methods + .filterNot { + it.name.contains("getId") + || it.name.contains("setId") + || it.name.contains("getOperations") + } + .toMutableList() + val companionMethods = map(companion).methods + val implemented = mutableListOf() + + javaMethods + .forEach { + if (!it.isStatic()) { + if (it in kotlinMethods) { + kotlinMethods -= it + implemented += it + } + } else { + if (it in companionMethods) { + companionMethods -= it + implemented += it + } + } + } + javaMethods.removeIf { + it.name == "findByIdOptional" || + it in implemented + } + + methods("javaMethods", javaMethods) + methods("kotlinMethods", kotlinMethods) + methods("companionMethods", companionMethods) + + assertTrue(javaMethods.isEmpty(), "New methods not implemented: ${javaMethods}") + assertTrue(kotlinMethods.isEmpty(), "Old methods not removed: ${kotlinMethods}") + assertTrue(companionMethods.isEmpty(), "Old methods not removed: ${companionMethods}") + } + + private fun map(type: KClass<*>): AnalogVisitor { + return AnalogVisitor().also { node -> + ClassReader(type.bytes()).accept(node, SKIP_CODE) + } + } + + + private fun KClass<*>.bytes() = + java.classLoader.getResourceAsStream(qualifiedName.toString().replace(".", "/") + ".class") + + private fun compare(javaClass: AnalogVisitor, kotlinClass: AnalogVisitor, whiteList: List = listOf()) { + val javaMethods = javaClass.methods + val kotlinMethods = kotlinClass.methods + val implemented = mutableListOf() + + javaMethods + .forEach { + if (it in kotlinMethods) { + kotlinMethods -= it + implemented += it + } + } + + javaMethods.removeIf { + it.name in whiteList || + it in implemented + } + + assertTrue(javaMethods.isEmpty(), "New methods not implemented: ${javaMethods}") + assertTrue(kotlinMethods.isEmpty(), "Old methods not removed: ${kotlinMethods}") + } + + @Suppress("unused") + private fun methods(label: String, methods: List) { + if (methods.isNotEmpty()) { + println("$label: ") + methods + .forEach { + println(it) + } + println() + } + } +} + +class AnalogVisitor : ClassVisitor(Gizmo.ASM_API_VERSION) { + val erasures = mapOf( + getType(PanacheMongoEntityBase::class.java).descriptor to getType(Object::class.java).descriptor, + getType(ReactivePanacheMongoEntityBase::class.java).descriptor to getType(Object::class.java).descriptor + ) + + val methods = mutableListOf() + override fun visitMethod(access: Int, name: String, descriptor: String, signature: String?, + exceptions: Array?): MethodVisitor? { + if (name != "" && name != "" && !descriptor.endsWith(ByteCodeType(Optional::class.java).descriptor())) { + val method = Method(access, name, erase(getReturnType(descriptor)), erase(getArgumentTypes(descriptor))) + methods += method + } + return super.visitMethod(access, name, descriptor, signature, exceptions) + } + + private fun erase(type: Type): String { + var value = type.descriptor + erasures.entries.forEach(Consumer { + value = value.replace(it.key, it.value) + }) + return value + } + + private fun erase(types: Array): List = types.map { erase(it) } +} + +class Method(val access: Int, val name: String, val type: String, val parameters: List) { + fun isStatic() = access.matches(Opcodes.ACC_STATIC) + + override fun toString(): String { + return (if (isStatic()) "static " else "") + "fun ${name}(${parameters.joinToString(", ")})" + + (if (type != Unit::class.qualifiedName) ": $type" else "") + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Method) return false + + if (name != other.name) return false + if (parameters != other.parameters) return false + + return true + } + + override fun hashCode(): Int { + var result = name.hashCode() + result = 31 * result + type.hashCode() + result = 31 * result + parameters.hashCode() + return result + } + +} + +fun Int.matches(mask: Int) = (this and mask) == mask + +fun Int.accDecode(): List { + val decode: MutableList = ArrayList() + val values: MutableMap = LinkedHashMap() + try { + for (f in Opcodes::class.java.declaredFields) { + if (f.name.startsWith("ACC_")) { + values[f.name] = f.getInt(Opcodes::class.java) + } + } + } catch (e: IllegalAccessException) { + throw RuntimeException(e.message, e) + } + for ((key, value) in values) { + if (this.matches(value)) { + decode.add(key) + } + } + return decode +} \ No newline at end of file diff --git a/extensions/panache/mongodb-panache/deployment/pom.xml b/extensions/panache/mongodb-panache/deployment/pom.xml index 00d4134365b65..b6d8093f7286c 100644 --- a/extensions/panache/mongodb-panache/deployment/pom.xml +++ b/extensions/panache/mongodb-panache/deployment/pom.xml @@ -16,7 +16,7 @@ io.quarkus - quarkus-core-deployment + quarkus-mongodb-panache-common-deployment io.quarkus diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ImperativeTypeBundle.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ImperativeTypeBundle.java new file mode 100644 index 0000000000000..9de8c13a00216 --- /dev/null +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ImperativeTypeBundle.java @@ -0,0 +1,61 @@ +package io.quarkus.mongodb.panache.deployment; + +import io.quarkus.mongodb.panache.PanacheMongoEntity; +import io.quarkus.mongodb.panache.PanacheMongoEntityBase; +import io.quarkus.mongodb.panache.PanacheMongoRepository; +import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase; +import io.quarkus.mongodb.panache.PanacheQuery; +import io.quarkus.mongodb.panache.PanacheUpdate; +import io.quarkus.mongodb.panache.runtime.MongoOperations; + +public class ImperativeTypeBundle implements TypeBundle { + @Override + public ByteCodeType entity() { + return new ByteCodeType(PanacheMongoEntity.class); + } + + @Override + public ByteCodeType entityBase() { + return new ByteCodeType(PanacheMongoEntityBase.class); + } + + @Override + public ByteCodeType entityBaseCompanion() { + throw new UnsupportedOperationException("Companions are not supported in Java."); + } + + @Override + public ByteCodeType entityCompanion() { + throw new UnsupportedOperationException("Companions are not supported in Java."); + } + + @Override + public ByteCodeType entityCompanionBase() { + throw new UnsupportedOperationException("Companions are not supported in Java."); + } + + @Override + public ByteCodeType operations() { + return new ByteCodeType(MongoOperations.class); + } + + @Override + public ByteCodeType queryType() { + return new ByteCodeType(PanacheQuery.class); + } + + @Override + public ByteCodeType repository() { + return new ByteCodeType(PanacheMongoRepository.class); + } + + @Override + public ByteCodeType repositoryBase() { + return new ByteCodeType(PanacheMongoRepositoryBase.class); + } + + @Override + public ByteCodeType updateType() { + return new ByteCodeType(PanacheUpdate.class); + } +} diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java index 78cf0614bea9d..37f4c83ab31e8 100644 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoEntityEnhancer.java @@ -1,21 +1,18 @@ package io.quarkus.mongodb.panache.deployment; +import static io.quarkus.mongodb.panache.deployment.BasePanacheMongoResourceProcessor.BSON_IGNORE; + import java.lang.reflect.Modifier; -import java.util.HashMap; import java.util.List; -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.runtime.MongoOperations; +import io.quarkus.mongodb.panache.deployment.visitors.PanacheMongoEntityClassVisitor; import io.quarkus.panache.common.deployment.EntityField; import io.quarkus.panache.common.deployment.EntityModel; import io.quarkus.panache.common.deployment.MetamodelInfo; @@ -23,63 +20,30 @@ import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; public class PanacheMongoEntityEnhancer extends PanacheEntityEnhancer>> { - public final static String MONGO_OPERATIONS_NAME = MongoOperations.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<>(); + private final TypeBundle typeBundle; - public PanacheMongoEntityEnhancer(IndexView index, List methodCustomizers) { - super(index, PanacheMongoResourceProcessor.DOTNAME_PANACHE_ENTITY_BASE, methodCustomizers); + public PanacheMongoEntityEnhancer(IndexView index, List methodCustomizers, + TypeBundle typeBundle) { + super(index, methodCustomizers); + this.typeBundle = typeBundle; modelInfo = new MetamodelInfo<>(); } @Override public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { - return new PanacheMongoEntityClassVisitor(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, - indexView.getClassByName(DotName.createSimple(className)), methodCustomizers); - } - - static class PanacheMongoEntityClassVisitor extends PanacheEntityClassVisitor { - - public PanacheMongoEntityClassVisitor(String className, ClassVisitor outputClassVisitor, - MetamodelInfo> modelInfo, ClassInfo panacheEntityBaseClassInfo, - ClassInfo entityInfo, List methodCustomizers) { - super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo, methodCustomizers); - } - - @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); - } + return new PanacheMongoEntityClassVisitor(className, outputClassVisitor, modelInfo, + indexView.getClassByName(typeBundle.entityBase().dotName()), + indexView.getClassByName(DotName.createSimple(className)), methodCustomizers, + typeBundle); } + @Override 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)) { + if (Modifier.isPublic(fieldInfo.flags()) && !fieldInfo.hasAnnotation(BSON_IGNORE)) { entityModel.addField(new EntityField(name, DescriptorUtils.typeToString(fieldInfo.type()))); } } diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoRepositoryEnhancer.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoRepositoryEnhancer.java index 3bb6919d4c618..7ca22195bf3af 100644 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoRepositoryEnhancer.java +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoRepositoryEnhancer.java @@ -1,61 +1,23 @@ 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.PanacheMongoRepository; -import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase; +import io.quarkus.mongodb.panache.deployment.visitors.PanacheMongoRepositoryClassVisitor; import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; public class PanacheMongoRepositoryEnhancer extends PanacheRepositoryEnhancer { - public final static DotName PANACHE_REPOSITORY_BASE_NAME = DotName.createSimple(PanacheMongoRepositoryBase.class.getName()); + private final TypeBundle typeBundle; - public final static DotName PANACHE_REPOSITORY_NAME = DotName.createSimple(PanacheMongoRepository.class.getName()); - - public PanacheMongoRepositoryEnhancer(IndexView index) { - super(index, PanacheMongoResourceProcessor.DOTNAME_PANACHE_REPOSITORY_BASE); + public PanacheMongoRepositoryEnhancer(IndexView index, TypeBundle typeBundle) { + super(index, typeBundle.repositoryBase().dotName()); + this.typeBundle = typeBundle; } @Override public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { - return new PanacheMongoRepositoryClassVisitor(className, outputClassVisitor, panacheRepositoryBaseClassInfo, - this.indexView); + return new PanacheMongoRepositoryClassVisitor(className, outputClassVisitor, + this.indexView, typeBundle); } - 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 PanacheMongoEntityEnhancer.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/PanacheMongoResourceProcessor.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoResourceProcessor.java index ef59d5ded6f82..0ec2566b51181 100644 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoResourceProcessor.java +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheMongoResourceProcessor.java @@ -1,464 +1,58 @@ package io.quarkus.mongodb.panache.deployment; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import org.bson.codecs.pojo.annotations.BsonId; -import org.bson.codecs.pojo.annotations.BsonProperty; -import org.bson.types.ObjectId; -import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationValue; -import org.jboss.jandex.ClassInfo; -import org.jboss.jandex.DotName; -import org.jboss.jandex.FieldInfo; -import org.jboss.jandex.IndexView; -import org.jboss.jandex.MethodInfo; -import org.jboss.jandex.Type; - -import io.quarkus.arc.deployment.ValidationPhaseBuildItem; -import io.quarkus.bootstrap.classloading.ClassPathElement; -import io.quarkus.bootstrap.classloading.QuarkusClassLoader; -import io.quarkus.builder.BuildException; import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; -import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.ExecutionTime; -import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.bean.JavaBeanUtil; -import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem; -import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; -import io.quarkus.deployment.builditem.ApplicationIndexBuildItem; -import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; import io.quarkus.deployment.builditem.CapabilityBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; -import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; -import io.quarkus.deployment.util.JandexUtil; -import io.quarkus.jackson.spi.JacksonModuleBuildItem; -import io.quarkus.jsonb.spi.JsonbDeserializerBuildItem; -import io.quarkus.jsonb.spi.JsonbSerializerBuildItem; -import io.quarkus.mongodb.deployment.MongoClientNameBuildItem; -import io.quarkus.mongodb.deployment.MongoUnremovableClientsBuildItem; -import io.quarkus.mongodb.panache.MongoEntity; -import io.quarkus.mongodb.panache.PanacheMongoEntity; -import io.quarkus.mongodb.panache.PanacheMongoEntityBase; -import io.quarkus.mongodb.panache.PanacheMongoRecorder; -import io.quarkus.mongodb.panache.PanacheMongoRepository; -import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase; -import io.quarkus.mongodb.panache.ProjectionFor; -import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntity; -import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntityBase; -import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepository; -import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepositoryBase; -import io.quarkus.panache.common.deployment.PanacheEntityClassesBuildItem; -import io.quarkus.panache.common.deployment.PanacheFieldAccessEnhancer; +import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; -import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem; import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer; -public class PanacheMongoResourceProcessor { - // blocking types - static final DotName DOTNAME_PANACHE_REPOSITORY_BASE = DotName.createSimple(PanacheMongoRepositoryBase.class.getName()); - private static final DotName DOTNAME_PANACHE_REPOSITORY = DotName.createSimple(PanacheMongoRepository.class.getName()); - static final DotName DOTNAME_PANACHE_ENTITY_BASE = DotName.createSimple(PanacheMongoEntityBase.class.getName()); - private static final DotName DOTNAME_PANACHE_ENTITY = DotName.createSimple(PanacheMongoEntity.class.getName()); - - private static final DotName DOTNAME_PROJECTION_FOR = DotName.createSimple(ProjectionFor.class.getName()); - private static final DotName DOTNAME_BSON_PROPERTY = DotName.createSimple(BsonProperty.class.getName()); - private static final DotName DOTNAME_BSON_ID = DotName.createSimple(BsonId.class.getName()); - - private static final DotName DOTNAME_MONGO_ENTITY = DotName.createSimple(MongoEntity.class.getName()); - - // reactive types (Mutiny) - static final DotName DOTNAME_MUTINY_PANACHE_REPOSITORY_BASE = DotName - .createSimple(ReactivePanacheMongoRepositoryBase.class.getName()); - private static final DotName DOTNAME_MUTINY_PANACHE_REPOSITORY = DotName - .createSimple(ReactivePanacheMongoRepository.class.getName()); - static final DotName DOTNAME_MUTINY_PANACHE_ENTITY_BASE = DotName - .createSimple(ReactivePanacheMongoEntityBase.class.getName()); - private static final DotName DOTNAME_MUTINY_PANACHE_ENTITY = DotName - .createSimple(ReactivePanacheMongoEntity.class.getName()); - - private static final DotName DOTNAME_OBJECT_ID = DotName.createSimple(ObjectId.class.getName()); - protected static final String META_INF_PANACHE_ARCHIVE_MARKER = "META-INF/panache-archive.marker"; - - @BuildStep - CapabilityBuildItem capability() { - return new CapabilityBuildItem(Capability.MONGODB_PANACHE); - } - - @BuildStep - FeatureBuildItem featureBuildItem() { - return new FeatureBuildItem(Feature.MONGODB_PANACHE); - } - - @BuildStep - void contributeClassesToIndex(BuildProducer additionalIndexedClasses) { - additionalIndexedClasses.produce(new AdditionalIndexedClassesBuildItem( - DOTNAME_OBJECT_ID.toString())); - } - - @BuildStep - void registerJsonbSerDeser(BuildProducer jsonbSerializers, - BuildProducer jsonbDeserializers) { - jsonbSerializers - .produce(new JsonbSerializerBuildItem(io.quarkus.mongodb.panache.jsonb.ObjectIdSerializer.class.getName())); - jsonbDeserializers - .produce(new JsonbDeserializerBuildItem(io.quarkus.mongodb.panache.jsonb.ObjectIdDeserializer.class.getName())); - } +public class PanacheMongoResourceProcessor extends BasePanacheMongoResourceProcessor { + public static final ImperativeTypeBundle IMPERATIVE_TYPE_BUNDLE = new ImperativeTypeBundle(); + public static final ReactiveTypeBundle REACTIVE_TYPE_BUNDLE = new ReactiveTypeBundle(); - @BuildStep - void registerJacksonSerDeser(BuildProducer customSerDeser) { - customSerDeser.produce( - new JacksonModuleBuildItem.Builder("ObjectIdModule") - .add(io.quarkus.mongodb.panache.jackson.ObjectIdSerializer.class.getName(), - io.quarkus.mongodb.panache.jackson.ObjectIdDeserializer.class.getName(), - ObjectId.class.getName()) - .build()); + protected ReactiveTypeBundle getReactiveTypeBundle() { + return REACTIVE_TYPE_BUNDLE; } - @BuildStep - ReflectiveHierarchyBuildItem registerForReflection(CombinedIndexBuildItem index) { - Type type = Type.create(DOTNAME_OBJECT_ID, Type.Kind.CLASS); - return new ReflectiveHierarchyBuildItem(type, index.getIndex()); + protected ImperativeTypeBundle getImperativeTypeBundle() { + return IMPERATIVE_TYPE_BUNDLE; } - @BuildStep - void unremoveableClients(BuildProducer unremovable) { - unremovable.produce(new MongoUnremovableClientsBuildItem()); + @Override + public PanacheMongoEntityEnhancer createEntityEnhancer(CombinedIndexBuildItem index, + List methodCustomizers) { + return new PanacheMongoEntityEnhancer(index.getIndex(), methodCustomizers, getImperativeTypeBundle()); } - @BuildStep - public void mongoClientNames(ApplicationArchivesBuildItem applicationArchivesBuildItem, - BuildProducer mongoClientName) { - Set values = new HashSet<>(); - IndexView indexView = applicationArchivesBuildItem.getRootArchive().getIndex(); - Collection instances = indexView.getAnnotations(DOTNAME_MONGO_ENTITY); - for (AnnotationInstance annotation : instances) { - AnnotationValue clientName = annotation.value("clientName"); - if ((clientName != null) && !clientName.asString().isEmpty()) { - values.add(clientName.asString()); - } - } - for (String value : values) { - // we don't want the qualifier @MongoClientName qualifier added - // as these clients will only be looked up programmatically via name - // see MongoOperations#mongoClient - mongoClientName.produce(new MongoClientNameBuildItem(value, false)); - } + @Override + public PanacheEntityEnhancer createReactiveEntityEnhancer(CombinedIndexBuildItem index, + List methodCustomizers) { + return new PanacheMongoEntityEnhancer(index.getIndex(), methodCustomizers, getReactiveTypeBundle()); } - @BuildStep - void collectEntityClasses(CombinedIndexBuildItem index, BuildProducer entityClasses) { - // NOTE: we don't skip abstract/generic entities because they still need accessors - for (ClassInfo panacheEntityBaseSubclass : index.getIndex().getAllKnownSubclasses(DOTNAME_PANACHE_ENTITY_BASE)) { - // FIXME: should we really skip PanacheEntity or all MappedSuperClass? - if (!panacheEntityBaseSubclass.name().equals(DOTNAME_PANACHE_ENTITY)) { - entityClasses.produce(new PanacheMongoEntityClassBuildItem(panacheEntityBaseSubclass)); - } - } + @Override + public PanacheMongoRepositoryEnhancer createReactiveRepositoryEnhancer(CombinedIndexBuildItem index) { + return new PanacheMongoRepositoryEnhancer(index.getIndex(), getReactiveTypeBundle()); } - @BuildStep - PanacheEntityClassesBuildItem findEntityClasses(List entityClasses) { - if (!entityClasses.isEmpty()) { - Set ret = new HashSet<>(); - for (PanacheMongoEntityClassBuildItem entityClass : entityClasses) { - ret.add(entityClass.get().name().toString()); - } - return new PanacheEntityClassesBuildItem(ret); - } - return null; + @Override + public PanacheRepositoryEnhancer createRepositoryEnhancer(CombinedIndexBuildItem index) { + return new PanacheMongoRepositoryEnhancer(index.getIndex(), getImperativeTypeBundle()); } @BuildStep - void buildImperative(CombinedIndexBuildItem index, - ApplicationIndexBuildItem applicationIndex, - BuildProducer transformers, - BuildProducer reflectiveClass, - BuildProducer propertyMappingClass, - List entityClasses, - List methodCustomizersBuildItems) { - - List methodCustomizers = methodCustomizersBuildItems.stream() - .map(bi -> bi.getMethodCustomizer()).collect(Collectors.toList()); - - PanacheMongoRepositoryEnhancer daoEnhancer = new PanacheMongoRepositoryEnhancer(index.getIndex()); - Set daoClasses = new HashSet<>(); - Set daoTypeParameters = new HashSet<>(); - for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_PANACHE_REPOSITORY_BASE)) { - // Skip PanacheMongoRepository and abstract repositories - if (classInfo.name().equals(DOTNAME_PANACHE_REPOSITORY) || PanacheRepositoryEnhancer.skipRepository(classInfo)) { - continue; - } - daoClasses.add(classInfo.name().toString()); - daoTypeParameters.addAll( - JandexUtil.resolveTypeParameters(classInfo.name(), DOTNAME_PANACHE_REPOSITORY_BASE, index.getIndex())); - } - for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_PANACHE_REPOSITORY)) { - if (PanacheRepositoryEnhancer.skipRepository(classInfo)) { - continue; - } - daoClasses.add(classInfo.name().toString()); - daoTypeParameters.addAll( - JandexUtil.resolveTypeParameters(classInfo.name(), DOTNAME_PANACHE_REPOSITORY_BASE, index.getIndex())); - } - for (String daoClass : daoClasses) { - transformers.produce(new BytecodeTransformerBuildItem(daoClass, daoEnhancer)); - } - - for (Type parameterType : daoTypeParameters) { - // Register for reflection the type parameters of the repository: this should be the entity class and the ID class - reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, parameterType.name().toString())); - - // Register for building the property mapping cache - propertyMappingClass.produce(new PropertyMappingClassBuildStep(parameterType.name().toString())); - } - - PanacheMongoEntityEnhancer modelEnhancer = new PanacheMongoEntityEnhancer(index.getIndex(), methodCustomizers); - Set modelClasses = new HashSet<>(); - Set modelClassNamesInternal = new HashSet<>(); - - for (PanacheMongoEntityClassBuildItem entityClass : entityClasses) { - String entityClassName = entityClass.get().name().toString(); - modelClasses.add(entityClassName); - modelEnhancer.collectFields(entityClass.get()); - modelClassNamesInternal.add(entityClassName.replace(".", "/")); - transformers.produce(new BytecodeTransformerBuildItem(entityClassName, modelEnhancer)); - //register for reflection entity classes - reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, entityClassName)); - // Register for building the property mapping cache - propertyMappingClass.produce(new PropertyMappingClassBuildStep(entityClassName)); - } - - if (!modelEnhancer.entities.isEmpty()) { - PanacheFieldAccessEnhancer panacheFieldAccessEnhancer = new PanacheFieldAccessEnhancer( - modelEnhancer.getModelInfo()); - QuarkusClassLoader tccl = (QuarkusClassLoader) Thread.currentThread().getContextClassLoader(); - List archives = tccl.getElementsWithResource(META_INF_PANACHE_ARCHIVE_MARKER); - for (ClassPathElement i : archives) { - for (String res : i.getProvidedResources()) { - if (res.endsWith(".class")) { - String cn = res.replace("/", ".").substring(0, res.length() - 6); - if (!modelClasses.contains(cn)) { - transformers.produce( - new BytecodeTransformerBuildItem(cn, panacheFieldAccessEnhancer, modelClassNamesInternal)); - } - } - } - } - } - } - - @BuildStep - void buildMutiny(CombinedIndexBuildItem index, - ApplicationIndexBuildItem applicationIndex, - BuildProducer reflectiveClass, - BuildProducer propertyMappingClass, - BuildProducer transformers, - List methodCustomizersBuildItems) { - - List methodCustomizers = methodCustomizersBuildItems.stream() - .map(bi -> bi.getMethodCustomizer()).collect(Collectors.toList()); - ReactivePanacheMongoRepositoryEnhancer daoEnhancer = new ReactivePanacheMongoRepositoryEnhancer(index.getIndex()); - Set daoClasses = new HashSet<>(); - Set daoTypeParameters = new HashSet<>(); - for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_MUTINY_PANACHE_REPOSITORY_BASE)) { - // Skip ReactivePanacheMongoRepository and abstract repositories - if (classInfo.name().equals(DOTNAME_MUTINY_PANACHE_REPOSITORY) - || PanacheRepositoryEnhancer.skipRepository(classInfo)) { - continue; - } - daoClasses.add(classInfo.name().toString()); - daoTypeParameters.addAll( - JandexUtil.resolveTypeParameters(classInfo.name(), DOTNAME_PANACHE_REPOSITORY_BASE, index.getIndex())); - } - for (ClassInfo classInfo : index.getIndex().getAllKnownImplementors(DOTNAME_MUTINY_PANACHE_REPOSITORY)) { - if (PanacheRepositoryEnhancer.skipRepository(classInfo)) { - continue; - } - daoClasses.add(classInfo.name().toString()); - daoTypeParameters.addAll( - JandexUtil.resolveTypeParameters(classInfo.name(), DOTNAME_PANACHE_REPOSITORY_BASE, index.getIndex())); - } - for (String daoClass : daoClasses) { - transformers.produce(new BytecodeTransformerBuildItem(daoClass, daoEnhancer)); - } - - for (Type parameterType : daoTypeParameters) { - // Register for reflection the type parameters of the repository: this should be the entity class and the ID class - reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, parameterType.name().toString())); - - // Register for building the property mapping cache - propertyMappingClass.produce(new PropertyMappingClassBuildStep(parameterType.name().toString())); - } - - ReactivePanacheMongoEntityEnhancer modelEnhancer = new ReactivePanacheMongoEntityEnhancer(index.getIndex(), - methodCustomizers); - 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); - } - - // iterate over all the entity classes - for (String modelClass : modelClasses) { - transformers.produce(new BytecodeTransformerBuildItem(modelClass, modelEnhancer)); - - //register for reflection entity classes - reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, modelClass)); - - // Register for building the property mapping cache - propertyMappingClass.produce(new PropertyMappingClassBuildStep(modelClass)); - } - - 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)); - } - } - } - } - - @BuildStep - ValidationPhaseBuildItem.ValidationErrorBuildItem validate(ValidationPhaseBuildItem validationPhase, - CombinedIndexBuildItem index) throws BuildException { - // we verify that no ID fields are defined (via @BsonId) when extending PanacheMongoEntity or ReactivePanacheMongoEntity - for (AnnotationInstance annotationInstance : index.getIndex().getAnnotations(DOTNAME_BSON_ID)) { - ClassInfo info = JandexUtil.getEnclosingClass(annotationInstance); - if (JandexUtil.isSubclassOf(index.getIndex(), info, - DOTNAME_PANACHE_ENTITY)) { - BuildException be = new BuildException("You provide a MongoDB identifier via @BsonId inside '" + info.name() + - "' but one is already provided by PanacheMongoEntity, " + - "your class should extend PanacheMongoEntityBase instead, or use the id provided by PanacheMongoEntity", - Collections.emptyList()); - return new ValidationPhaseBuildItem.ValidationErrorBuildItem(be); - } else if (JandexUtil.isSubclassOf(index.getIndex(), info, - DOTNAME_MUTINY_PANACHE_ENTITY)) { - BuildException be = new BuildException("You provide a MongoDB identifier via @BsonId inside '" + info.name() + - "' but one is already provided by ReactivePanacheMongoEntity, " + - "your class should extend ReactivePanacheMongoEntityBase instead, or use the id provided by ReactivePanacheMongoEntity", - Collections.emptyList()); - return new ValidationPhaseBuildItem.ValidationErrorBuildItem(be); - } - } - return null; - } - - @BuildStep - void handleProjectionFor(CombinedIndexBuildItem index, - BuildProducer propertyMappingClass, - BuildProducer transformers) { - // manage @BsonProperty for the @ProjectionFor annotation - Map> propertyMapping = new HashMap<>(); - for (AnnotationInstance annotationInstance : index.getIndex().getAnnotations(DOTNAME_PROJECTION_FOR)) { - Type targetClass = annotationInstance.value().asClass(); - ClassInfo target = index.getIndex().getClassByName(targetClass.name()); - Map classPropertyMapping = new HashMap<>(); - extractMappings(classPropertyMapping, target, index); - propertyMapping.put(targetClass.name(), classPropertyMapping); - } - for (AnnotationInstance annotationInstance : index.getIndex().getAnnotations(DOTNAME_PROJECTION_FOR)) { - Type targetClass = annotationInstance.value().asClass(); - Map targetPropertyMapping = propertyMapping.get(targetClass.name()); - if (targetPropertyMapping != null && !targetPropertyMapping.isEmpty()) { - ClassInfo info = annotationInstance.target().asClass(); - ProjectionForEnhancer fieldEnhancer = new ProjectionForEnhancer(targetPropertyMapping); - transformers.produce(new BytecodeTransformerBuildItem(info.name().toString(), fieldEnhancer)); - } - - // Register for building the property mapping cache - propertyMappingClass - .produce(new PropertyMappingClassBuildStep(targetClass.name().toString(), - annotationInstance.target().asClass().name().toString())); - } - } - - private void extractMappings(Map classPropertyMapping, ClassInfo target, CombinedIndexBuildItem index) { - for (FieldInfo fieldInfo : target.fields()) { - if (fieldInfo.hasAnnotation(DOTNAME_BSON_PROPERTY)) { - AnnotationInstance bsonProperty = fieldInfo.annotation(DOTNAME_BSON_PROPERTY); - classPropertyMapping.put(fieldInfo.name(), bsonProperty.value().asString()); - } - } - for (MethodInfo methodInfo : target.methods()) { - if (methodInfo.hasAnnotation(DOTNAME_BSON_PROPERTY)) { - AnnotationInstance bsonProperty = methodInfo.annotation(DOTNAME_BSON_PROPERTY); - classPropertyMapping.put(methodInfo.name(), bsonProperty.value().asString()); - } - } - - // climb up the hierarchy of types - if (!target.superClassType().name().equals(JandexUtil.DOTNAME_OBJECT)) { - Type superType = target.superClassType(); - ClassInfo superClass = index.getIndex().getClassByName(superType.name()); - extractMappings(classPropertyMapping, superClass, index); - } + protected CapabilityBuildItem capability() { + return new CapabilityBuildItem(Capability.MONGODB_PANACHE); } @BuildStep - @Record(ExecutionTime.STATIC_INIT) - void buildReplacementMap(List propertyMappingClasses, CombinedIndexBuildItem index, - PanacheMongoRecorder recorder) { - Map> replacementMap = new ConcurrentHashMap<>(); - for (PropertyMappingClassBuildStep classToMap : propertyMappingClasses) { - DotName dotName = DotName.createSimple(classToMap.getClassName()); - ClassInfo classInfo = index.getIndex().getClassByName(dotName); - if (classInfo != null) { - // only compute field replacement for types inside the index - Map classReplacementMap = replacementMap.computeIfAbsent(classToMap.getClassName(), - className -> computeReplacement(classInfo)); - if (classToMap.getAliasClassName() != null) { - // also register the replacement map for the projection classes - replacementMap.put(classToMap.getAliasClassName(), classReplacementMap); - } - } - } - - recorder.setReplacementCache(replacementMap); - } - - private Map computeReplacement(ClassInfo classInfo) { - Map replacementMap = new HashMap<>(); - for (FieldInfo field : classInfo.fields()) { - AnnotationInstance bsonProperty = field.annotation(DOTNAME_BSON_PROPERTY); - if (bsonProperty != null) { - replacementMap.put(field.name(), bsonProperty.value().asString()); - } - } - for (MethodInfo method : classInfo.methods()) { - if (method.name().startsWith("get")) { - // we try to replace also for getter - AnnotationInstance bsonProperty = method.annotation(DOTNAME_BSON_PROPERTY); - if (bsonProperty != null) { - String fieldName = JavaBeanUtil.decapitalize(method.name().substring(3)); - replacementMap.put(fieldName, bsonProperty.value().asString()); - } - } - } - return replacementMap.isEmpty() ? Collections.emptyMap() : replacementMap; + protected FeatureBuildItem featureBuildItem() { + return new FeatureBuildItem(Feature.MONGODB_PANACHE); } - } diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java deleted file mode 100644 index 739d898fd5aa7..0000000000000 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java +++ /dev/null @@ -1,88 +0,0 @@ -package io.quarkus.mongodb.panache.deployment; - -import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.List; -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; -import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; - -public class ReactivePanacheMongoEntityEnhancer extends PanacheEntityEnhancer>> { - public final static String MONGO_OPERATIONS_NAME = ReactiveMongoOperations.class.getName(); - public final static String MONGO_OPERATIONS_BINARY_NAME = MONGO_OPERATIONS_NAME.replace('.', '/'); - - private static final DotName DOTNAME_BSON_IGNORE = DotName.createSimple(BsonIgnore.class.getName()); - - final Map entities = new HashMap<>(); - - public ReactivePanacheMongoEntityEnhancer(IndexView index, List methodCustomizers) { - super(index, PanacheMongoResourceProcessor.DOTNAME_MUTINY_PANACHE_ENTITY_BASE, methodCustomizers); - modelInfo = new MetamodelInfo<>(); - } - - @Override - public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { - return new PanacheMongoEntityClassVisitor(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, - indexView.getClassByName(DotName.createSimple(className)), methodCustomizers); - } - - static class PanacheMongoEntityClassVisitor extends PanacheEntityClassVisitor { - - public PanacheMongoEntityClassVisitor(String className, ClassVisitor outputClassVisitor, - MetamodelInfo> modelInfo, ClassInfo panacheEntityBaseClassInfo, - ClassInfo entityInfo, List methodCustomizers) { - super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo, methodCustomizers); - } - - @Override - protected void injectModel(MethodVisitor mv) { - mv.visitLdcInsn(thisClass); - } - - @Override - protected String getModelDescriptor() { - return "Ljava/lang/Class;"; - } - - @Override - protected String getPanacheOperationsBinaryName() { - return MONGO_OPERATIONS_BINARY_NAME; - } - - @Override - protected void generateAccessorSetField(MethodVisitor mv, EntityField field) { - mv.visitFieldInsn(Opcodes.PUTFIELD, thisClass.getInternalName(), field.name, field.descriptor); - } - - @Override - protected void generateAccessorGetField(MethodVisitor mv, EntityField field) { - mv.visitFieldInsn(Opcodes.GETFIELD, thisClass.getInternalName(), field.name, field.descriptor); - } - } - - public void collectFields(ClassInfo classInfo) { - EntityModel entityModel = new EntityModel<>(classInfo); - for (FieldInfo fieldInfo : classInfo.fields()) { - String name = fieldInfo.name(); - if (Modifier.isPublic(fieldInfo.flags()) && !fieldInfo.hasAnnotation(DOTNAME_BSON_IGNORE)) { - entityModel.addField(new EntityField(name, DescriptorUtils.typeToString(fieldInfo.type()))); - } - } - modelInfo.addEntityModel(entityModel); - } -} diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoRepositoryEnhancer.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoRepositoryEnhancer.java deleted file mode 100644 index 4cc24f7903381..0000000000000 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoRepositoryEnhancer.java +++ /dev/null @@ -1,62 +0,0 @@ -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 ReactivePanacheMongoRepositoryEnhancer extends PanacheRepositoryEnhancer { - public final static DotName PANACHE_REPOSITORY_BASE_NAME = DotName - .createSimple(ReactivePanacheMongoRepositoryBase.class.getName()); - - public final static DotName PANACHE_REPOSITORY_NAME = DotName.createSimple(ReactivePanacheMongoRepository.class.getName()); - - public ReactivePanacheMongoRepositoryEnhancer(IndexView index) { - super(index, PanacheMongoResourceProcessor.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 ReactivePanacheMongoEntityEnhancer.MONGO_OPERATIONS_BINARY_NAME; - } - - @Override - protected void injectModel(MethodVisitor mv) { - // inject Class - mv.visitLdcInsn(entityType); - } - - @Override - protected String getModelDescriptor() { - return "Ljava/lang/Class;"; - } - } -} diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactiveTypeBundle.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactiveTypeBundle.java new file mode 100644 index 0000000000000..bf6b7a32f5746 --- /dev/null +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactiveTypeBundle.java @@ -0,0 +1,61 @@ +package io.quarkus.mongodb.panache.deployment; + +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntity; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntityBase; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepository; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepositoryBase; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheUpdate; +import io.quarkus.mongodb.panache.reactive.runtime.ReactiveMongoOperations; + +public class ReactiveTypeBundle implements TypeBundle { + @Override + public ByteCodeType entity() { + return new ByteCodeType(ReactivePanacheMongoEntity.class); + } + + @Override + public ByteCodeType entityBase() { + return new ByteCodeType(ReactivePanacheMongoEntityBase.class); + } + + @Override + public ByteCodeType entityBaseCompanion() { + throw new UnsupportedOperationException("Companions are not supported in Java."); + } + + @Override + public ByteCodeType entityCompanion() { + throw new UnsupportedOperationException("Companions are not supported in Java."); + } + + @Override + public ByteCodeType entityCompanionBase() { + throw new UnsupportedOperationException("Companions are not supported in Java."); + } + + @Override + public ByteCodeType operations() { + return new ByteCodeType(ReactiveMongoOperations.class); + } + + @Override + public ByteCodeType queryType() { + return new ByteCodeType(ReactivePanacheQuery.class); + } + + @Override + public ByteCodeType repository() { + return new ByteCodeType(ReactivePanacheMongoRepository.class); + } + + @Override + public ByteCodeType repositoryBase() { + return new ByteCodeType(ReactivePanacheMongoRepositoryBase.class); + } + + @Override + public ByteCodeType updateType() { + return new ByteCodeType(ReactivePanacheUpdate.class); + } +} diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/visitors/PanacheMongoRepositoryClassVisitor.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/visitors/PanacheMongoRepositoryClassVisitor.java new file mode 100644 index 0000000000000..59892656e0cc1 --- /dev/null +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/visitors/PanacheMongoRepositoryClassVisitor.java @@ -0,0 +1,120 @@ +package io.quarkus.mongodb.panache.deployment.visitors; + +import static io.quarkus.deployment.util.AsmUtil.getDescriptor; +import static io.quarkus.mongodb.panache.deployment.BasePanacheMongoResourceProcessor.OBJECT_SIGNATURE; +import static java.util.Arrays.asList; +import static org.objectweb.asm.Opcodes.CHECKCAST; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; + +import java.util.List; +import java.util.StringJoiner; +import java.util.function.Function; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import io.quarkus.deployment.util.AsmUtil; +import io.quarkus.mongodb.panache.deployment.ByteCodeType; +import io.quarkus.mongodb.panache.deployment.TypeBundle; +import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; +import io.quarkus.panache.common.deployment.visitors.PanacheRepositoryClassVisitor; + +public class PanacheMongoRepositoryClassVisitor extends PanacheRepositoryClassVisitor { + private static final ByteCodeType CLASS = new ByteCodeType(Class.class); + + private final TypeBundle typeBundle; + + public PanacheMongoRepositoryClassVisitor(String className, ClassVisitor outputClassVisitor, + IndexView indexView, TypeBundle typeBundle) { + super(className, outputClassVisitor, indexView); + this.typeBundle = typeBundle; + } + + @Override + protected void generateModelBridge(MethodInfo method, AnnotationValue targetReturnTypeErased) { + String descriptor = AsmUtil.getDescriptor(method, name -> typeArguments.get(name)); + // JpaOperations erases the Id type to Object + String descriptorForJpaOperations = AsmUtil.getDescriptor(method, + name -> name.equals("Entity") ? entitySignature : null); + String signature = AsmUtil.getSignature(method, name -> typeArguments.get(name)); + List parameters = method.parameters(); + + // Note: we can't use SYNTHETIC here because otherwise Mockito will never mock these methods + MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, + method.name(), + descriptor, + signature, + null); + for (int i = 0; i < parameters.size(); i++) { + mv.visitParameter(method.parameterName(i), 0 /* modifiers */); + } + mv.visitFieldInsn(Opcodes.GETSTATIC, daoBinaryName, "operations", + typeBundle.operations().descriptor()); + mv.visitCode(); + injectModel(mv); + for (int i = 0; i < parameters.size(); i++) { + mv.visitIntInsn(Opcodes.ALOAD, i + 1); + } + invokeOperation(mv, method); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + private void invokeOperation(MethodVisitor mv, MethodInfo method) { + String operationDescriptor; + Function argMapper = type -> null; + + AnnotationInstance bridge = method.annotation(PanacheEntityEnhancer.DOTNAME_GENERATE_BRIDGE); + AnnotationValue targetReturnTypeErased = bridge.value("targetReturnTypeErased"); + boolean erased = targetReturnTypeErased != null && targetReturnTypeErased.asBoolean(); + + StringJoiner joiner = new StringJoiner("", "(", ")"); + joiner.add(CLASS.descriptor()); + for (Type parameter : method.parameters()) { + joiner.add(getDescriptor(parameter, argMapper)); + } + + List names = asList(typeBundle.queryType().dotName().toString(), + typeBundle.updateType().dotName().toString()); + operationDescriptor = joiner + + (erased || names.contains(method.returnType().name().toString()) + ? OBJECT_SIGNATURE + : getDescriptor(method.returnType(), argMapper)); + + mv.visitMethodInsn(INVOKEVIRTUAL, typeBundle.operations().internalName(), method.name(), + operationDescriptor, false); + if (method.returnType().kind() != Type.Kind.PRIMITIVE) { + Type type = method.returnType(); + String cast; + if (erased) { + cast = entityBinaryType; + } else { + cast = type.name().toString().replace('.', '/'); + } + mv.visitTypeInsn(CHECKCAST, cast); + } + mv.visitInsn(AsmUtil.getReturnInstruction(method.returnType())); + } + + @Override + protected DotName getPanacheRepositoryDotName() { + return typeBundle.repository().dotName(); + } + + @Override + protected DotName getPanacheRepositoryBaseDotName() { + return typeBundle.repositoryBase().dotName(); + } + + @Override + protected String getPanacheOperationsInternalName() { + return typeBundle.operations().internalName(); + } +} diff --git a/extensions/panache/mongodb-panache/runtime/pom.xml b/extensions/panache/mongodb-panache/runtime/pom.xml index 773e2dff4bca3..001df22a8dcac 100644 --- a/extensions/panache/mongodb-panache/runtime/pom.xml +++ b/extensions/panache/mongodb-panache/runtime/pom.xml @@ -24,11 +24,7 @@ io.quarkus - quarkus-mongodb-client - - - io.quarkus - quarkus-panacheql + quarkus-mongodb-panache-common diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java index b057466896d0d..f9e42ea6c3e2d 100755 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoEntityBase.java @@ -10,6 +10,7 @@ import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import io.quarkus.mongodb.panache.runtime.JavaMongoOperations; import io.quarkus.mongodb.panache.runtime.MongoOperations; import io.quarkus.panache.common.Parameters; import io.quarkus.panache.common.Sort; @@ -17,26 +18,24 @@ /** * 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 PanacheMongoEntity} instead. + * 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 PanacheMongoEntity} instead. * * @see PanacheMongoEntity */ public abstract class PanacheMongoEntityBase { - - // Operations + protected static final MongoOperations operations = new JavaMongoOperations(); /** * Persist this entity in the database. - * This will set it's ID field if not already set. + * This will set its ID field if not already set. * * @see #persist(Iterable) * @see #persist(Stream) * @see #persist(Object, Object...) */ public void persist() { - MongoOperations.persist(this); + operations.persist(this); } /** @@ -47,7 +46,7 @@ public void persist() { * @see #update(Object, Object...) */ public void update() { - MongoOperations.update(this); + operations.update(this); } /** @@ -58,7 +57,7 @@ public void update() { * @see #persistOrUpdate(Object, Object...) */ public void persistOrUpdate() { - MongoOperations.persistOrUpdate(this); + operations.persistOrUpdate(this); } /** @@ -70,7 +69,7 @@ public void persistOrUpdate() { * @see #deleteAll() */ public void delete() { - MongoOperations.delete(this); + operations.delete(this); } // Queries @@ -83,7 +82,7 @@ public void delete() { */ @GenerateBridge(targetReturnTypeErased = true) public static T findById(Object id) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -94,7 +93,7 @@ public static T findById(Object id) { */ @GenerateBridge public static Optional findByIdOptional(Object id) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -111,7 +110,7 @@ public static Optional findByIdOptional(Ob */ @GenerateBridge public static PanacheQuery find(String query, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -129,7 +128,7 @@ public static PanacheQuery find(String que */ @GenerateBridge public static PanacheQuery find(String query, Sort sort, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -146,7 +145,7 @@ public static PanacheQuery find(String que */ @GenerateBridge public static PanacheQuery find(String query, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -164,7 +163,7 @@ public static PanacheQuery find(String que */ @GenerateBridge public static PanacheQuery find(String query, Sort sort, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -181,7 +180,7 @@ public static PanacheQuery find(String que */ @GenerateBridge public static PanacheQuery find(String query, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -199,7 +198,7 @@ public static PanacheQuery find(String que */ @GenerateBridge public static PanacheQuery find(String query, Sort sort, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -215,7 +214,7 @@ public static PanacheQuery find(String que */ @GenerateBridge public static PanacheQuery find(Document query) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -232,7 +231,7 @@ public static PanacheQuery find(Document q */ @GenerateBridge public static PanacheQuery find(Document query, Document sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -245,7 +244,7 @@ public static PanacheQuery find(Document q */ @GenerateBridge public static PanacheQuery findAll() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -259,7 +258,7 @@ public static PanacheQuery findAll() { */ @GenerateBridge public static PanacheQuery findAll(Sort sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -277,7 +276,7 @@ public static PanacheQuery findAll(Sort so */ @GenerateBridge public static List list(String query, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -296,7 +295,7 @@ public static List list(String query, Obje */ @GenerateBridge public static List list(String query, Sort sort, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -314,7 +313,7 @@ public static List list(String query, Sort */ @GenerateBridge public static List list(String query, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -333,7 +332,7 @@ public static List list(String query, Map< */ @GenerateBridge public static List list(String query, Sort sort, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -351,7 +350,7 @@ public static List list(String query, Sort */ @GenerateBridge public static List list(String query, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -370,7 +369,7 @@ public static List list(String query, Para */ @GenerateBridge public static List list(String query, Sort sort, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -387,7 +386,7 @@ public static List list(String query, Sort */ @GenerateBridge public static List list(Document query) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -405,7 +404,7 @@ public static List list(Document query) { */ @GenerateBridge public static List list(Document query, Document sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -419,7 +418,7 @@ public static List list(Document query, Do */ @GenerateBridge public static List listAll() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -434,7 +433,7 @@ public static List listAll() { */ @GenerateBridge public static List listAll(Sort sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -452,7 +451,7 @@ public static List listAll(Sort sort) { */ @GenerateBridge public static Stream stream(String query, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -471,7 +470,7 @@ public static Stream stream(String query, */ @GenerateBridge public static Stream stream(String query, Sort sort, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -489,7 +488,7 @@ public static Stream stream(String query, */ @GenerateBridge public static Stream stream(String query, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -508,7 +507,7 @@ public static Stream stream(String query, */ @GenerateBridge public static Stream stream(String query, Sort sort, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -526,7 +525,7 @@ public static Stream stream(String query, */ @GenerateBridge public static Stream stream(String query, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -545,7 +544,7 @@ public static Stream stream(String query, */ @GenerateBridge public static Stream stream(String query, Sort sort, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -562,7 +561,7 @@ public static Stream stream(String query, */ @GenerateBridge public static Stream stream(Document query) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -580,7 +579,7 @@ public static Stream stream(Document query */ @GenerateBridge public static Stream stream(Document query, Document sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -594,7 +593,7 @@ public static Stream stream(Document query */ @GenerateBridge public static Stream streamAll() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -609,7 +608,7 @@ public static Stream streamAll() { */ @GenerateBridge public static Stream streamAll(Sort sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -622,7 +621,7 @@ public static Stream streamAll(Sort sort) */ @GenerateBridge public static long count() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -637,7 +636,7 @@ public static long count() { */ @GenerateBridge public static long count(String query, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -652,7 +651,7 @@ public static long count(String query, Object... params) { */ @GenerateBridge public static long count(String query, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -667,7 +666,7 @@ public static long count(String query, Map params) { */ @GenerateBridge public static long count(String query, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -681,7 +680,7 @@ public static long count(String query, Parameters params) { */ @GenerateBridge public static long count(Document query) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -694,7 +693,7 @@ public static long count(Document query) { */ @GenerateBridge public static long deleteAll() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -705,7 +704,7 @@ public static long deleteAll() { */ @GenerateBridge public static boolean deleteById(Object id) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -720,7 +719,7 @@ public static boolean deleteById(Object id) { */ @GenerateBridge public static long delete(String query, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -735,7 +734,7 @@ public static long delete(String query, Object... params) { */ @GenerateBridge public static long delete(String query, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -750,7 +749,7 @@ public static long delete(String query, Map params) { */ @GenerateBridge public static long delete(String query, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -764,7 +763,7 @@ public static long delete(String query, Parameters params) { */ @GenerateBridge public static long delete(Document query) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -776,7 +775,7 @@ public static long delete(Document query) { * @see #persist(Object,Object...) */ public static void persist(Iterable entities) { - MongoOperations.persist(entities); + operations.persist(entities); } /** @@ -788,7 +787,7 @@ public static void persist(Iterable entities) { * @see #persist(Object,Object...) */ public static void persist(Stream entities) { - MongoOperations.persist(entities); + operations.persist(entities); } /** @@ -800,7 +799,7 @@ public static void persist(Stream entities) { * @see #persist(Iterable) */ public static void persist(Object firstEntity, Object... entities) { - MongoOperations.persist(firstEntity, entities); + operations.persist(firstEntity, entities); } /** @@ -812,7 +811,7 @@ public static void persist(Object firstEntity, Object... entities) { * @see #update(Object,Object...) */ public static void update(Iterable entities) { - MongoOperations.update(entities); + operations.update(entities); } /** @@ -824,7 +823,7 @@ public static void update(Iterable entities) { * @see #update(Object,Object...) */ public static void update(Stream entities) { - MongoOperations.update(entities); + operations.update(entities); } /** @@ -836,7 +835,7 @@ public static void update(Stream entities) { * @see #update(Iterable) */ public static void update(Object firstEntity, Object... entities) { - MongoOperations.update(firstEntity, entities); + operations.update(firstEntity, entities); } /** @@ -848,7 +847,7 @@ public static void update(Object firstEntity, Object... entities) { * @see #persistOrUpdate(Object,Object...) */ public static void persistOrUpdate(Iterable entities) { - MongoOperations.persistOrUpdate(entities); + operations.persistOrUpdate(entities); } /** @@ -860,7 +859,7 @@ public static void persistOrUpdate(Iterable entities) { * @see #persistOrUpdate(Object,Object...) */ public static void persistOrUpdate(Stream entities) { - MongoOperations.persistOrUpdate(entities); + operations.persistOrUpdate(entities); } /** @@ -872,7 +871,7 @@ public static void persistOrUpdate(Stream entities) { * @see #persistOrUpdate(Iterable) */ public static void persistOrUpdate(Object firstEntity, Object... entities) { - MongoOperations.persistOrUpdate(firstEntity, entities); + operations.persistOrUpdate(firstEntity, entities); } /** @@ -888,7 +887,7 @@ public static void persistOrUpdate(Object firstEntity, Object... entities) { */ @GenerateBridge public static PanacheUpdate update(String update, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -905,7 +904,7 @@ public static PanacheUpdate update(String update, Object... params) { */ @GenerateBridge public static PanacheUpdate update(String update, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -921,7 +920,7 @@ public static PanacheUpdate update(String update, Map params) { */ @GenerateBridge public static PanacheUpdate update(String update, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -929,7 +928,7 @@ public static PanacheUpdate update(String update, Parameters params) { */ @GenerateBridge public static MongoCollection mongoCollection() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -937,6 +936,6 @@ public static MongoCollection mongoCollect */ @GenerateBridge public static MongoDatabase mongoDatabase() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } } diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRepositoryBase.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRepositoryBase.java index 7c2f7d1ebe58d..2465f5f8971a2 100755 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRepositoryBase.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/PanacheMongoRepositoryBase.java @@ -10,6 +10,7 @@ import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import io.quarkus.mongodb.panache.runtime.JavaMongoOperations; import io.quarkus.mongodb.panache.runtime.MongoOperations; import io.quarkus.panache.common.Parameters; import io.quarkus.panache.common.Sort; @@ -26,8 +27,7 @@ * @see PanacheMongoRepository */ public interface PanacheMongoRepositoryBase { - - // Operations + MongoOperations operations = new JavaMongoOperations(); /** * Persist the given entity in the database. @@ -39,7 +39,7 @@ public interface PanacheMongoRepositoryBase { * @see #persist(Object, Object...) */ public default void persist(Entity entity) { - MongoOperations.persist(entity); + operations.persist(entity); } /** @@ -51,7 +51,7 @@ public default void persist(Entity entity) { * @see #update(Object, Object...) */ public default void update(Entity entity) { - MongoOperations.update(entity); + operations.update(entity); } /** @@ -63,7 +63,7 @@ public default void update(Entity entity) { * @see #persistOrUpdate(Object, Object...) */ public default void persistOrUpdate(Entity entity) { - MongoOperations.persistOrUpdate(entity); + operations.persistOrUpdate(entity); } /** @@ -76,7 +76,7 @@ public default void persistOrUpdate(Entity entity) { * @see #deleteAll() */ public default void delete(Entity entity) { - MongoOperations.delete(entity); + operations.delete(entity); } // Queries @@ -89,7 +89,7 @@ public default void delete(Entity entity) { */ @GenerateBridge(targetReturnTypeErased = true) public default Entity findById(Id id) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -100,7 +100,7 @@ public default Entity findById(Id id) { */ @GenerateBridge public default Optional findByIdOptional(Id id) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -117,7 +117,7 @@ public default Optional findByIdOptional(Id id) { */ @GenerateBridge public default PanacheQuery find(String query, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -135,7 +135,7 @@ public default PanacheQuery find(String query, Object... params) { */ @GenerateBridge public default PanacheQuery find(String query, Sort sort, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -152,7 +152,7 @@ public default PanacheQuery find(String query, Sort sort, Object... para */ @GenerateBridge public default PanacheQuery find(String query, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -170,7 +170,7 @@ public default PanacheQuery find(String query, Map param */ @GenerateBridge public default PanacheQuery find(String query, Sort sort, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -187,7 +187,7 @@ public default PanacheQuery find(String query, Sort sort, Map find(String query, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -205,7 +205,7 @@ public default PanacheQuery find(String query, Parameters params) { */ @GenerateBridge public default PanacheQuery find(String query, Sort sort, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -221,7 +221,7 @@ public default PanacheQuery find(String query, Sort sort, Parameters par */ @GenerateBridge public default PanacheQuery find(Document query) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -238,7 +238,7 @@ public default PanacheQuery find(Document query) { */ @GenerateBridge public default PanacheQuery find(Document query, Document sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -251,7 +251,7 @@ public default PanacheQuery find(Document query, Document sort) { */ @GenerateBridge public default PanacheQuery findAll() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -265,7 +265,7 @@ public default PanacheQuery findAll() { */ @GenerateBridge public default PanacheQuery findAll(Sort sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -283,7 +283,7 @@ public default PanacheQuery findAll(Sort sort) { */ @GenerateBridge public default List list(String query, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -302,7 +302,7 @@ public default List list(String query, Object... params) { */ @GenerateBridge public default List list(String query, Sort sort, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -320,7 +320,7 @@ public default List list(String query, Sort sort, Object... params) { */ @GenerateBridge public default List list(String query, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -339,7 +339,7 @@ public default List list(String query, Map params) { */ @GenerateBridge public default List list(String query, Sort sort, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -357,7 +357,7 @@ public default List list(String query, Sort sort, Map pa */ @GenerateBridge public default List list(String query, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -376,7 +376,7 @@ public default List list(String query, Parameters params) { */ @GenerateBridge public default List list(String query, Sort sort, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -393,7 +393,7 @@ public default List list(String query, Sort sort, Parameters params) { */ @GenerateBridge public default List list(Document query) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -411,7 +411,7 @@ public default List list(Document query) { */ @GenerateBridge public default List list(Document query, Document sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -425,7 +425,7 @@ public default List list(Document query, Document sort) { */ @GenerateBridge public default List listAll() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -440,7 +440,7 @@ public default List listAll() { */ @GenerateBridge public default List listAll(Sort sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -458,7 +458,7 @@ public default List listAll(Sort sort) { */ @GenerateBridge public default Stream stream(String query, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -477,7 +477,7 @@ public default Stream stream(String query, Object... params) { */ @GenerateBridge public default Stream stream(String query, Sort sort, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -495,7 +495,7 @@ public default Stream stream(String query, Sort sort, Object... params) */ @GenerateBridge public default Stream stream(String query, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -514,7 +514,7 @@ public default Stream stream(String query, Map params) { */ @GenerateBridge public default Stream stream(String query, Sort sort, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -532,7 +532,7 @@ public default Stream stream(String query, Sort sort, Map stream(String query, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -551,7 +551,7 @@ public default Stream stream(String query, Parameters params) { */ @GenerateBridge public default Stream stream(String query, Sort sort, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -568,7 +568,7 @@ public default Stream stream(String query, Sort sort, Parameters params) */ @GenerateBridge public default Stream stream(Document query) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -586,7 +586,7 @@ public default Stream stream(Document query) { */ @GenerateBridge public default Stream stream(Document query, Document sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -600,7 +600,7 @@ public default Stream stream(Document query, Document sort) { */ @GenerateBridge public default Stream streamAll(Sort sort) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -614,7 +614,7 @@ public default Stream streamAll(Sort sort) { */ @GenerateBridge public default Stream streamAll() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -627,7 +627,7 @@ public default Stream streamAll() { */ @GenerateBridge public default long count() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -642,7 +642,7 @@ public default long count() { */ @GenerateBridge public default long count(String query, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -657,7 +657,7 @@ public default long count(String query, Object... params) { */ @GenerateBridge public default long count(String query, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -672,7 +672,7 @@ public default long count(String query, Map params) { */ @GenerateBridge public default long count(String query, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -686,7 +686,7 @@ public default long count(String query, Parameters params) { */ @GenerateBridge public default long count(Document query) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -699,7 +699,7 @@ public default long count(Document query) { */ @GenerateBridge public default long deleteAll() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -710,7 +710,7 @@ public default long deleteAll() { */ @GenerateBridge public default boolean deleteById(Id id) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -725,7 +725,7 @@ public default boolean deleteById(Id id) { */ @GenerateBridge public default long delete(String query, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -740,7 +740,7 @@ public default long delete(String query, Object... params) { */ @GenerateBridge public default long delete(String query, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -755,7 +755,7 @@ public default long delete(String query, Map params) { */ @GenerateBridge public default long delete(String query, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -769,7 +769,7 @@ public default long delete(String query, Parameters params) { */ @GenerateBridge public default long delete(Document query) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -781,7 +781,7 @@ public default long delete(Document query) { * @see #persist(Object,Object...) */ public default void persist(Iterable entities) { - MongoOperations.persist(entities); + operations.persist(entities); } /** @@ -793,7 +793,7 @@ public default void persist(Iterable entities) { * @see #persist(Object,Object...) */ public default void persist(Stream entities) { - MongoOperations.persist(entities); + operations.persist(entities); } /** @@ -804,8 +804,8 @@ public default void persist(Stream entities) { * @see #persist(Stream) * @see #persist(Iterable) */ - public default void persist(Entity firstEntity, @SuppressWarnings("unchecked") Entity... entities) { - MongoOperations.persist(firstEntity, entities); + public default void persist(Entity firstEntity, Entity... entities) { + operations.persist(firstEntity, entities); } /** @@ -817,7 +817,7 @@ public default void persist(Entity firstEntity, @SuppressWarnings("unchecked") E * @see #update(Object,Object...) */ public default void update(Iterable entities) { - MongoOperations.update(entities); + operations.update(entities); } /** @@ -829,7 +829,7 @@ public default void update(Iterable entities) { * @see #update(Object,Object...) */ public default void update(Stream entities) { - MongoOperations.update(entities); + operations.update(entities); } /** @@ -841,7 +841,7 @@ public default void update(Stream entities) { * @see #update(Iterable) */ public default void update(Entity firstEntity, @SuppressWarnings("unchecked") Entity... entities) { - MongoOperations.update(firstEntity, entities); + operations.update(firstEntity, entities); } /** @@ -853,7 +853,7 @@ public default void update(Entity firstEntity, @SuppressWarnings("unchecked") En * @see #persistOrUpdate(Object,Object...) */ public default void persistOrUpdate(Iterable entities) { - MongoOperations.persistOrUpdate(entities); + operations.persistOrUpdate(entities); } /** @@ -865,7 +865,7 @@ public default void persistOrUpdate(Iterable entities) { * @see #persistOrUpdate(Object,Object...) */ public default void persistOrUpdate(Stream entities) { - MongoOperations.persistOrUpdate(entities); + operations.persistOrUpdate(entities); } /** @@ -877,7 +877,7 @@ public default void persistOrUpdate(Stream entities) { * @see #update(Iterable) */ public default void persistOrUpdate(Entity firstEntity, @SuppressWarnings("unchecked") Entity... entities) { - MongoOperations.persistOrUpdate(firstEntity, entities); + operations.persistOrUpdate(firstEntity, entities); } /** @@ -893,7 +893,7 @@ public default void persistOrUpdate(Entity firstEntity, @SuppressWarnings("unche */ @GenerateBridge public default PanacheUpdate update(String update, Object... params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -910,7 +910,7 @@ public default PanacheUpdate update(String update, Object... params) { */ @GenerateBridge public default PanacheUpdate update(String update, Map params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -926,7 +926,7 @@ public default PanacheUpdate update(String update, Map params) { */ @GenerateBridge public default PanacheUpdate update(String update, Parameters params) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -934,7 +934,7 @@ public default PanacheUpdate update(String update, Parameters params) { */ @GenerateBridge public default MongoCollection mongoCollection() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -942,6 +942,6 @@ public default MongoCollection mongoCollection() { */ @GenerateBridge public default MongoDatabase mongoDatabase() { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } } 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 index b4f95eecb8101..e24eb454403ce 100644 --- 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 @@ -8,8 +8,8 @@ import org.bson.Document; import io.quarkus.mongodb.panache.PanacheUpdate; +import io.quarkus.mongodb.panache.reactive.runtime.JavaReactiveMongoOperations; import io.quarkus.mongodb.panache.reactive.runtime.ReactiveMongoOperations; -import io.quarkus.mongodb.panache.runtime.MongoOperations; import io.quarkus.mongodb.reactive.ReactiveMongoCollection; import io.quarkus.mongodb.reactive.ReactiveMongoDatabase; import io.quarkus.panache.common.Parameters; @@ -26,20 +26,20 @@ * * @see ReactivePanacheMongoEntity */ +@SuppressWarnings({ "rawtypes", "unchecked" }) public abstract class ReactivePanacheMongoEntityBase { - - // Operations + protected static final ReactiveMongoOperations operations = new JavaReactiveMongoOperations(); /** * Persist this entity in the database. - * This will set it's ID field if not already set. + * This will set its ID field if not already set. * * @see #persist(Iterable) * @see #persist(Stream) * @see #persist(Object, Object...) */ public Uni persist() { - return ReactiveMongoOperations.persist(this); + return operations.persist(this); } /** @@ -50,7 +50,7 @@ public Uni persist() { * @see #update(Object, Object...) */ public Uni update() { - return ReactiveMongoOperations.update(this); + return operations.update(this); } /** @@ -61,7 +61,7 @@ public Uni update() { * @see #persistOrUpdate(Object, Object...) */ public Uni persistOrUpdate() { - return ReactiveMongoOperations.persistOrUpdate(this); + return operations.persistOrUpdate(this); } /** @@ -73,7 +73,7 @@ public Uni persistOrUpdate() { * @see #deleteAll() */ public Uni delete() { - return ReactiveMongoOperations.delete(this); + return operations.delete(this); } // Queries @@ -86,7 +86,7 @@ public Uni delete() { */ @GenerateBridge public static Uni findById(Object id) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -97,7 +97,7 @@ public static Uni findById(Object */ @GenerateBridge public static Uni> findByIdOptional(Object id) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -114,7 +114,7 @@ public static Uni> findBy */ @GenerateBridge public static ReactivePanacheQuery find(String query, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -133,7 +133,7 @@ public static ReactivePanacheQuery @GenerateBridge public static ReactivePanacheQuery find(String query, Sort sort, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -151,7 +151,7 @@ public static ReactivePanacheQuery @GenerateBridge public static ReactivePanacheQuery find(String query, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -170,7 +170,7 @@ public static ReactivePanacheQuery @GenerateBridge public static ReactivePanacheQuery find(String query, Sort sort, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -187,7 +187,7 @@ public static ReactivePanacheQuery */ @GenerateBridge public static ReactivePanacheQuery find(String query, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -206,7 +206,7 @@ public static ReactivePanacheQuery @GenerateBridge public static ReactivePanacheQuery find(String query, Sort sort, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -222,7 +222,7 @@ public static ReactivePanacheQuery */ @GenerateBridge public static ReactivePanacheQuery find(Document query) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -239,7 +239,7 @@ public static ReactivePanacheQuery */ @GenerateBridge public static ReactivePanacheQuery find(Document query, Document sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -252,7 +252,7 @@ public static ReactivePanacheQuery */ @GenerateBridge public static ReactivePanacheQuery findAll() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -266,7 +266,7 @@ public static ReactivePanacheQuery */ @GenerateBridge public static ReactivePanacheQuery findAll(Sort sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -284,7 +284,7 @@ public static ReactivePanacheQuery */ @GenerateBridge public static Uni> list(String query, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -304,7 +304,7 @@ public static Uni> list(Strin @GenerateBridge public static Uni> list(String query, Sort sort, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -323,7 +323,7 @@ public static Uni> list(Strin @GenerateBridge public static Uni> list(String query, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -343,7 +343,7 @@ public static Uni> list(Strin @GenerateBridge public static Uni> list(String query, Sort sort, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -361,7 +361,7 @@ public static Uni> list(Strin */ @GenerateBridge public static Uni> list(String query, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -381,7 +381,7 @@ public static Uni> list(Strin @GenerateBridge public static Uni> list(String query, Sort sort, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -398,7 +398,7 @@ public static Uni> list(Strin */ @GenerateBridge public static Uni> list(Document query) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -416,7 +416,7 @@ public static Uni> list(Docum */ @GenerateBridge public static Uni> list(Document query, Document sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -430,7 +430,7 @@ public static Uni> list(Docum */ @GenerateBridge public static Uni> listAll() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -445,7 +445,7 @@ public static Uni> listAll() */ @GenerateBridge public static Uni> listAll(Sort sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -463,7 +463,7 @@ public static Uni> listAll(So */ @GenerateBridge public static Multi stream(String query, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -482,7 +482,7 @@ public static Multi stream(String */ @GenerateBridge public static Multi stream(String query, Sort sort, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -500,7 +500,7 @@ public static Multi stream(String */ @GenerateBridge public static Multi stream(String query, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -520,7 +520,7 @@ public static Multi stream(String @GenerateBridge public static Multi stream(String query, Sort sort, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -538,7 +538,7 @@ public static Multi stream(String */ @GenerateBridge public static Multi stream(String query, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -557,7 +557,7 @@ public static Multi stream(String */ @GenerateBridge public static Multi stream(String query, Sort sort, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -574,7 +574,7 @@ public static Multi stream(String */ @GenerateBridge public static Multi stream(Document query) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -592,7 +592,7 @@ public static Multi stream(Documen */ @GenerateBridge public static Multi stream(Document query, Document sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -606,7 +606,7 @@ public static Multi stream(Documen */ @GenerateBridge public static Multi streamAll() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -621,7 +621,7 @@ public static Multi streamAll() { */ @GenerateBridge public static Multi streamAll(Sort sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -634,7 +634,7 @@ public static Multi streamAll(Sort */ @GenerateBridge public static Uni count() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -649,7 +649,7 @@ public static Uni count() { */ @GenerateBridge public static Uni count(String query, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -664,7 +664,7 @@ public static Uni count(String query, Object... params) { */ @GenerateBridge public static Uni count(String query, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -679,7 +679,7 @@ public static Uni count(String query, Map params) { */ @GenerateBridge public static Uni count(String query, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -693,7 +693,7 @@ public static Uni count(String query, Parameters params) { */ @GenerateBridge public static Uni count(Document query) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -706,7 +706,7 @@ public static Uni count(Document query) { */ @GenerateBridge public static Uni deleteAll() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -717,7 +717,7 @@ public static Uni deleteAll() { */ @GenerateBridge public static Uni deleteById(Object id) { - throw MongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -732,7 +732,7 @@ public static Uni deleteById(Object id) { */ @GenerateBridge public static Uni delete(String query, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -747,7 +747,7 @@ public static Uni delete(String query, Object... params) { */ @GenerateBridge public static Uni delete(String query, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -762,7 +762,7 @@ public static Uni delete(String query, Map params) { */ @GenerateBridge public static Uni delete(String query, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -776,7 +776,7 @@ public static Uni delete(String query, Parameters params) { */ @GenerateBridge public static Uni delete(Document query) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -788,7 +788,7 @@ public static Uni delete(Document query) { * @see #persist(Object,Object...) */ public static Uni persist(Iterable entities) { - return ReactiveMongoOperations.persist(entities); + return operations.persist(entities); } /** @@ -800,7 +800,7 @@ public static Uni persist(Iterable entities) { * @see #persist(Object,Object...) */ public static Uni persist(Stream entities) { - return ReactiveMongoOperations.persist(entities); + return operations.persist(entities); } /** @@ -812,7 +812,7 @@ public static Uni persist(Stream entities) { * @see #persist(Iterable) */ public static Uni persist(Object firstEntity, Object... entities) { - return ReactiveMongoOperations.persist(firstEntity, entities); + return operations.persist(firstEntity, entities); } /** @@ -824,7 +824,7 @@ public static Uni persist(Object firstEntity, Object... entities) { * @see #update(Object,Object...) */ public static Uni update(Iterable entities) { - return ReactiveMongoOperations.update(entities); + return operations.update(entities); } /** @@ -836,7 +836,7 @@ public static Uni update(Iterable entities) { * @see #update(Object,Object...) */ public static Uni update(Stream entities) { - return ReactiveMongoOperations.update(entities); + return operations.update(entities); } /** @@ -848,7 +848,7 @@ public static Uni update(Stream entities) { * @see #update(Iterable) */ public static Uni update(Object firstEntity, Object... entities) { - return ReactiveMongoOperations.update(firstEntity, entities); + return operations.update(firstEntity, entities); } /** @@ -860,7 +860,7 @@ public static Uni update(Object firstEntity, Object... entities) { * @see #persistOrUpdate(Object,Object...) */ public static Uni persistOrUpdate(Iterable entities) { - return ReactiveMongoOperations.persistOrUpdate(entities); + return operations.persistOrUpdate(entities); } /** @@ -872,7 +872,7 @@ public static Uni persistOrUpdate(Iterable entities) { * @see #persistOrUpdate(Object,Object...) */ public static Uni persistOrUpdate(Stream entities) { - return ReactiveMongoOperations.persistOrUpdate(entities); + return operations.persistOrUpdate(entities); } /** @@ -884,7 +884,7 @@ public static Uni persistOrUpdate(Stream entities) { * @see #persistOrUpdate(Iterable) */ public static Uni persistOrUpdate(Object firstEntity, Object... entities) { - return ReactiveMongoOperations.persistOrUpdate(firstEntity, entities); + return operations.persistOrUpdate(firstEntity, entities); } /** @@ -900,7 +900,7 @@ public static Uni persistOrUpdate(Object firstEntity, Object... entities) */ @GenerateBridge public static ReactivePanacheUpdate update(String update, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -917,7 +917,7 @@ public static ReactivePanacheUpdate update(String update, Object... params) { */ @GenerateBridge public static ReactivePanacheUpdate update(String update, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -933,7 +933,7 @@ public static ReactivePanacheUpdate update(String update, Map pa */ @GenerateBridge public static ReactivePanacheUpdate update(String update, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -941,7 +941,7 @@ public static ReactivePanacheUpdate update(String update, Parameters params) { */ @GenerateBridge public static ReactiveMongoCollection mongoCollection() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } /** @@ -949,6 +949,6 @@ public static ReactiveMongoCollection */ @GenerateBridge public static ReactiveMongoDatabase mongoDatabase() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + throw operations.implementationInjectionMissing(); } } 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 index 5f6e3fadafb51..69cb331218e1c 100644 --- 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 @@ -1,5 +1,7 @@ package io.quarkus.mongodb.panache.reactive; +import static io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntityBase.operations; + import java.util.List; import java.util.Map; import java.util.Optional; @@ -8,8 +10,8 @@ import org.bson.Document; import io.quarkus.mongodb.panache.PanacheUpdate; +import io.quarkus.mongodb.panache.reactive.runtime.JavaReactiveMongoOperations; import io.quarkus.mongodb.panache.reactive.runtime.ReactiveMongoOperations; -import io.quarkus.mongodb.panache.runtime.MongoOperations; import io.quarkus.mongodb.reactive.ReactiveMongoCollection; import io.quarkus.mongodb.reactive.ReactiveMongoDatabase; import io.quarkus.panache.common.Parameters; @@ -28,9 +30,9 @@ * @param The ID type of the entity * @see ReactivePanacheMongoRepository */ +@SuppressWarnings("unchecked") public interface ReactivePanacheMongoRepositoryBase { - - // Operations + ReactiveMongoOperations operations = new JavaReactiveMongoOperations(); /** * Persist the given entity in the database. @@ -41,8 +43,8 @@ public interface ReactivePanacheMongoRepositoryBase { * @see #persist(Stream) * @see #persist(Object, Object...) */ - public default Uni persist(Entity entity) { - return ReactiveMongoOperations.persist(entity); + default Uni persist(Entity entity) { + return operations.persist(entity); } /** @@ -53,8 +55,8 @@ public default Uni persist(Entity entity) { * @see #update(Stream) * @see #update(Object, Object...) */ - public default Uni update(Entity entity) { - return ReactiveMongoOperations.update(entity); + default Uni update(Entity entity) { + return operations.update(entity); } /** @@ -65,8 +67,8 @@ public default Uni update(Entity entity) { * @see #persistOrUpdate(Stream) * @see #persistOrUpdate(Object, Object...) */ - public default Uni persistOrUpdate(Entity entity) { - return ReactiveMongoOperations.persistOrUpdate(entity); + default Uni persistOrUpdate(Entity entity) { + return operations.persistOrUpdate(entity); } /** @@ -78,8 +80,8 @@ public default Uni persistOrUpdate(Entity entity) { * @see #delete(String, Parameters) * @see #deleteAll() */ - public default Uni delete(Entity entity) { - return ReactiveMongoOperations.delete(entity); + default Uni delete(Entity entity) { + return operations.delete(entity); } // Queries @@ -91,8 +93,8 @@ public default Uni delete(Entity entity) { * @return the entity found, or null if not found. */ @GenerateBridge - public default Uni findById(Id id) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni findById(Id id) { + throw operations.implementationInjectionMissing(); } /** @@ -102,8 +104,8 @@ public default Uni findById(Id id) { * @return if found, an optional containing the entity, else Optional.empty(). */ @GenerateBridge - public default Uni> findByIdOptional(Object id) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni> findByIdOptional(Object id) { + throw operations.implementationInjectionMissing(); } /** @@ -119,8 +121,8 @@ public default Uni> findByIdOptional(Object id) { * @see #stream(String, Object...) */ @GenerateBridge - public default ReactivePanacheQuery find(String query, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheQuery find(String query, Object... params) { + throw operations.implementationInjectionMissing(); } /** @@ -137,8 +139,8 @@ public default ReactivePanacheQuery find(String query, Object... params) * @see #stream(String, Sort, Object...) */ @GenerateBridge - public default ReactivePanacheQuery find(String query, Sort sort, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheQuery find(String query, Sort sort, Object... params) { + throw operations.implementationInjectionMissing(); } /** @@ -154,8 +156,8 @@ public default ReactivePanacheQuery find(String query, Sort sort, Object * @see #stream(String, Map) */ @GenerateBridge - public default ReactivePanacheQuery find(String query, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheQuery find(String query, Map params) { + throw operations.implementationInjectionMissing(); } /** @@ -172,8 +174,8 @@ public default ReactivePanacheQuery find(String query, Map find(String query, Sort sort, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheQuery find(String query, Sort sort, Map params) { + throw operations.implementationInjectionMissing(); } /** @@ -189,8 +191,8 @@ public default ReactivePanacheQuery find(String query, Sort sort, Map find(String query, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheQuery find(String query, Parameters params) { + throw operations.implementationInjectionMissing(); } /** @@ -207,8 +209,8 @@ public default ReactivePanacheQuery find(String query, Parameters params * @see #stream(String, Sort, Parameters) */ @GenerateBridge - public default ReactivePanacheQuery find(String query, Sort sort, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheQuery find(String query, Sort sort, Parameters params) { + throw operations.implementationInjectionMissing(); } /** @@ -223,8 +225,8 @@ public default ReactivePanacheQuery find(String query, Sort sort, Parame * @see #stream(Document, Document) */ @GenerateBridge - public default ReactivePanacheQuery find(Document query) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheQuery find(Document query) { + throw operations.implementationInjectionMissing(); } /** @@ -240,8 +242,8 @@ public default ReactivePanacheQuery find(Document query) { * @see #stream(Document, Document) */ @GenerateBridge - public default ReactivePanacheQuery find(Document query, Document sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheQuery find(Document query, Document sort) { + throw operations.implementationInjectionMissing(); } /** @@ -253,8 +255,8 @@ public default ReactivePanacheQuery find(Document query, Document sort) * @see #streamAll() */ @GenerateBridge - public default ReactivePanacheQuery findAll() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheQuery findAll() { + throw operations.implementationInjectionMissing(); } /** @@ -267,8 +269,8 @@ public default ReactivePanacheQuery findAll() { * @see #streamAll(Sort) */ @GenerateBridge - public default ReactivePanacheQuery findAll(Sort sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheQuery findAll(Sort sort) { + throw operations.implementationInjectionMissing(); } /** @@ -285,8 +287,8 @@ public default ReactivePanacheQuery findAll(Sort sort) { * @see #stream(String, Object...) */ @GenerateBridge - public default Uni> list(String query, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni> list(String query, Object... params) { + throw operations.implementationInjectionMissing(); } /** @@ -304,8 +306,8 @@ public default Uni> list(String query, Object... params) { * @see #stream(String, Sort, Object...) */ @GenerateBridge - public default Uni> list(String query, Sort sort, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni> list(String query, Sort sort, Object... params) { + throw operations.implementationInjectionMissing(); } /** @@ -322,8 +324,8 @@ public default Uni> list(String query, Sort sort, Object... params) * @see #stream(String, Map) */ @GenerateBridge - public default Uni> list(String query, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni> list(String query, Map params) { + throw operations.implementationInjectionMissing(); } /** @@ -341,8 +343,8 @@ public default Uni> list(String query, Map params) * @see #stream(String, Sort, Map) */ @GenerateBridge - public default Uni> list(String query, Sort sort, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni> list(String query, Sort sort, Map params) { + throw operations.implementationInjectionMissing(); } /** @@ -359,8 +361,8 @@ public default Uni> list(String query, Sort sort, Map> list(String query, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni> list(String query, Parameters params) { + throw operations.implementationInjectionMissing(); } /** @@ -378,8 +380,8 @@ public default Uni> list(String query, Parameters params) { * @see #stream(String, Sort, Parameters) */ @GenerateBridge - public default Uni> list(String query, Sort sort, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni> list(String query, Sort sort, Parameters params) { + throw operations.implementationInjectionMissing(); } /** @@ -395,8 +397,8 @@ public default Uni> list(String query, Sort sort, Parameters params * @see #stream(Document, Document) */ @GenerateBridge - public default Uni> list(Document query) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni> list(Document query) { + throw operations.implementationInjectionMissing(); } /** @@ -413,8 +415,8 @@ public default Uni> list(Document query) { * @see #stream(Document, Document) */ @GenerateBridge - public default Uni> list(Document query, Document sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni> list(Document query, Document sort) { + throw operations.implementationInjectionMissing(); } /** @@ -427,8 +429,8 @@ public default Uni> list(Document query, Document sort) { * @see #streamAll() */ @GenerateBridge - public default Uni> listAll() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni> listAll() { + throw operations.implementationInjectionMissing(); } /** @@ -442,8 +444,8 @@ public default Uni> listAll() { * @see #streamAll(Sort) */ @GenerateBridge - public default Uni> listAll(Sort sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni> listAll(Sort sort) { + throw operations.implementationInjectionMissing(); } /** @@ -460,8 +462,8 @@ public default Uni> listAll(Sort sort) { * @see #list(String, Object...) */ @GenerateBridge - public default Multi stream(String query, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Multi stream(String query, Object... params) { + throw operations.implementationInjectionMissing(); } /** @@ -479,8 +481,8 @@ public default Multi stream(String query, Object... params) { * @see #list(String, Sort, Object...) */ @GenerateBridge - public default Multi stream(String query, Sort sort, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Multi stream(String query, Sort sort, Object... params) { + throw operations.implementationInjectionMissing(); } /** @@ -497,8 +499,8 @@ public default Multi stream(String query, Sort sort, Object... params) { * @see #list(String, Map) */ @GenerateBridge - public default Multi stream(String query, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Multi stream(String query, Map params) { + throw operations.implementationInjectionMissing(); } /** @@ -516,8 +518,8 @@ public default Multi stream(String query, Map params) { * @see #list(String, Sort, Map) */ @GenerateBridge - public default Multi stream(String query, Sort sort, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Multi stream(String query, Sort sort, Map params) { + throw operations.implementationInjectionMissing(); } /** @@ -534,8 +536,8 @@ public default Multi stream(String query, Sort sort, Map * @see #list(String, Parameters) */ @GenerateBridge - public default Multi stream(String query, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Multi stream(String query, Parameters params) { + throw operations.implementationInjectionMissing(); } /** @@ -553,8 +555,8 @@ public default Multi stream(String query, Parameters params) { * @see #list(String, Sort, Parameters) */ @GenerateBridge - public default Multi stream(String query, Sort sort, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Multi stream(String query, Sort sort, Parameters params) { + throw operations.implementationInjectionMissing(); } /** @@ -571,8 +573,8 @@ public default Multi stream(String query, Sort sort, Parameters params) * @see #stream(Document, Document) */ @GenerateBridge - public default Multi stream(Document query) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Multi stream(Document query) { + throw operations.implementationInjectionMissing(); } /** @@ -590,8 +592,8 @@ public default Multi stream(Document query) { * @see #stream(Document, Document) */ @GenerateBridge - public default Multi stream(Document query, Document sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Multi stream(Document query, Document sort) { + throw operations.implementationInjectionMissing(); } /** @@ -604,8 +606,8 @@ public default Multi stream(Document query, Document sort) { * @see #listAll() */ @GenerateBridge - public default Multi streamAll(Sort sort) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Multi streamAll(Sort sort) { + throw operations.implementationInjectionMissing(); } /** @@ -618,8 +620,8 @@ public default Multi streamAll(Sort sort) { * @see #listAll(Sort) */ @GenerateBridge - public default Multi streamAll() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Multi streamAll() { + throw operations.implementationInjectionMissing(); } /** @@ -631,8 +633,8 @@ public default Multi streamAll() { * @see #count(String, Parameters) */ @GenerateBridge - public default Uni count() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni count() { + throw operations.implementationInjectionMissing(); } /** @@ -646,8 +648,8 @@ public default Uni count() { * @see #count(String, Parameters) */ @GenerateBridge - public default Uni count(String query, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni count(String query, Object... params) { + throw operations.implementationInjectionMissing(); } /** @@ -661,8 +663,8 @@ public default Uni count(String query, Object... params) { * @see #count(String, Parameters) */ @GenerateBridge - public default Uni count(String query, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni count(String query, Map params) { + throw operations.implementationInjectionMissing(); } /** @@ -676,8 +678,8 @@ public default Uni count(String query, Map params) { * @see #count(String, Map) */ @GenerateBridge - public default Uni count(String query, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni count(String query, Parameters params) { + throw operations.implementationInjectionMissing(); } /** @@ -690,8 +692,8 @@ public default Uni count(String query, Parameters params) { * @see #count(String, Map) */ @GenerateBridge - public default Uni count(Document query) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni count(Document query) { + throw operations.implementationInjectionMissing(); } /** @@ -703,8 +705,8 @@ public default Uni count(Document query) { * @see #delete(String, Parameters) */ @GenerateBridge - public default Uni deleteAll() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni deleteAll() { + throw operations.implementationInjectionMissing(); } /** @@ -714,8 +716,8 @@ public default Uni deleteAll() { * @return false if the entity was not deleted (not found). */ @GenerateBridge - public default Uni deleteById(Id id) { - throw MongoOperations.implementationInjectionMissing(); + default Uni deleteById(Id id) { + throw operations.implementationInjectionMissing(); } /** @@ -729,8 +731,8 @@ public default Uni deleteById(Id id) { * @see #delete(String, Parameters) */ @GenerateBridge - public default Uni delete(String query, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni delete(String query, Object... params) { + throw operations.implementationInjectionMissing(); } /** @@ -744,8 +746,8 @@ public default Uni delete(String query, Object... params) { * @see #delete(String, Parameters) */ @GenerateBridge - public default Uni delete(String query, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni delete(String query, Map params) { + throw operations.implementationInjectionMissing(); } /** @@ -759,8 +761,8 @@ public default Uni delete(String query, Map params) { * @see #delete(String, Map) */ @GenerateBridge - public default Uni delete(String query, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni delete(String query, Parameters params) { + throw operations.implementationInjectionMissing(); } /** @@ -773,8 +775,8 @@ public default Uni delete(String query, Parameters params) { * @see #count(String, Map) */ @GenerateBridge - public default Uni delete(Document query) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default Uni delete(Document query) { + throw operations.implementationInjectionMissing(); } /** @@ -785,8 +787,8 @@ public default Uni delete(Document query) { * @see #persist(Stream) * @see #persist(Object,Object...) */ - public default Uni persist(Iterable entities) { - return ReactiveMongoOperations.persist(entities); + default Uni persist(Iterable entities) { + return operations.persist(entities); } /** @@ -797,8 +799,8 @@ public default Uni persist(Iterable entities) { * @see #persist(Iterable) * @see #persist(Object,Object...) */ - public default Uni persist(Stream entities) { - return ReactiveMongoOperations.persist(entities); + default Uni persist(Stream entities) { + return operations.persist(entities); } /** @@ -809,8 +811,8 @@ public default Uni persist(Stream entities) { * @see #persist(Stream) * @see #persist(Iterable) */ - public default Uni persist(Entity firstEntity, @SuppressWarnings("unchecked") Entity... entities) { - return ReactiveMongoOperations.persist(firstEntity, entities); + default Uni persist(Entity firstEntity, Entity... entities) { + return operations.persist(firstEntity, entities); } /** @@ -821,8 +823,8 @@ public default Uni persist(Entity firstEntity, @SuppressWarnings("unchecke * @see #update(Stream) * @see #update(Object,Object...) */ - public default Uni update(Iterable entities) { - return ReactiveMongoOperations.update(entities); + default Uni update(Iterable entities) { + return operations.update(entities); } /** @@ -833,8 +835,8 @@ public default Uni update(Iterable entities) { * @see #update(Iterable) * @see #update(Object,Object...) */ - public default Uni update(Stream entities) { - return ReactiveMongoOperations.update(entities); + default Uni update(Stream entities) { + return operations.update(entities); } /** @@ -845,8 +847,8 @@ public default Uni update(Stream entities) { * @see #update(Stream) * @see #update(Iterable) */ - public default Uni update(Entity firstEntity, @SuppressWarnings("unchecked") Entity... entities) { - return ReactiveMongoOperations.update(firstEntity, entities); + default Uni update(Entity firstEntity, Entity... entities) { + return operations.update(firstEntity, entities); } /** @@ -857,8 +859,8 @@ public default Uni update(Entity firstEntity, @SuppressWarnings("unchecked * @see #persistOrUpdate(Stream) * @see #persistOrUpdate(Object,Object...) */ - public default Uni persistOrUpdate(Iterable entities) { - return ReactiveMongoOperations.persistOrUpdate(entities); + default Uni persistOrUpdate(Iterable entities) { + return operations.persistOrUpdate(entities); } /** @@ -869,8 +871,8 @@ public default Uni persistOrUpdate(Iterable entities) { * @see #persistOrUpdate(Iterable) * @see #persistOrUpdate(Object,Object...) */ - public default Uni persistOrUpdate(Stream entities) { - return ReactiveMongoOperations.persistOrUpdate(entities); + default Uni persistOrUpdate(Stream entities) { + return operations.persistOrUpdate(entities); } /** @@ -881,9 +883,8 @@ public default Uni persistOrUpdate(Stream entities) { * @see #update(Stream) * @see #update(Iterable) */ - public default Uni persistOrUpdate(Entity firstEntity, - @SuppressWarnings("unchecked") Entity... entities) { - return ReactiveMongoOperations.persistOrUpdate(firstEntity, entities); + default Uni persistOrUpdate(Entity firstEntity, Entity... entities) { + return operations.persistOrUpdate(firstEntity, entities); } /** @@ -898,8 +899,8 @@ public default Uni persistOrUpdate(Entity firstEntity, * @see #update(String, Parameters) */ @GenerateBridge - public default ReactivePanacheUpdate update(String update, Object... params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheUpdate update(String update, Object... params) { + throw operations.implementationInjectionMissing(); } /** @@ -915,8 +916,8 @@ public default ReactivePanacheUpdate update(String update, Object... params) { * */ @GenerateBridge - public default ReactivePanacheUpdate update(String update, Map params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheUpdate update(String update, Map params) { + throw operations.implementationInjectionMissing(); } /** @@ -931,23 +932,23 @@ public default ReactivePanacheUpdate update(String update, Map p * @see #update(String, Map) */ @GenerateBridge - public default ReactivePanacheUpdate update(String update, Parameters params) { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactivePanacheUpdate update(String update, Parameters params) { + throw operations.implementationInjectionMissing(); } /** * Allow to access the underlying Mongo Collection */ @GenerateBridge - public default ReactiveMongoCollection mongoCollection() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactiveMongoCollection mongoCollection() { + throw operations.implementationInjectionMissing(); } /** * Allow to access the underlying Mongo Database. */ @GenerateBridge - public default ReactiveMongoDatabase mongoDatabase() { - throw ReactiveMongoOperations.implementationInjectionMissing(); + default ReactiveMongoDatabase mongoDatabase() { + throw operations.implementationInjectionMissing(); } } diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/JavaReactiveMongoOperations.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/JavaReactiveMongoOperations.java new file mode 100644 index 0000000000000..780526bc7c83b --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/reactive/runtime/JavaReactiveMongoOperations.java @@ -0,0 +1,35 @@ +package io.quarkus.mongodb.panache.reactive.runtime; + +import java.util.List; + +import org.bson.Document; + +import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheUpdate; +import io.quarkus.mongodb.reactive.ReactiveMongoCollection; +import io.smallrye.mutiny.Multi; +import io.smallrye.mutiny.Uni; + +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class JavaReactiveMongoOperations extends ReactiveMongoOperations, ReactivePanacheUpdate> { + @Override + protected ReactivePanacheQuery createQuery(ReactiveMongoCollection collection, Document query, Document sortDoc) { + return new ReactivePanacheQueryImpl(collection, query, sortDoc); + } + + @Override + protected ReactivePanacheUpdate createUpdate(ReactiveMongoCollection collection, Class entityClass, + Document docUpdate) { + return new ReactivePanacheUpdateImpl(this, entityClass, docUpdate, collection); + } + + @Override + protected Uni> list(ReactivePanacheQuery query) { + return query.list(); + } + + @Override + protected Multi stream(ReactivePanacheQuery query) { + return query.stream(); + } +} 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 index 4ebd2b7d73ff0..bf3c08369f6ac 100644 --- 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 @@ -2,74 +2,38 @@ import java.util.List; import java.util.Optional; -import java.util.Set; -import org.bson.Document; import org.bson.conversions.Bson; import com.mongodb.client.model.Collation; -import io.quarkus.mongodb.FindOptions; import io.quarkus.mongodb.panache.reactive.ReactivePanacheQuery; -import io.quarkus.mongodb.panache.runtime.MongoPropertyUtil; import io.quarkus.mongodb.reactive.ReactiveMongoCollection; import io.quarkus.panache.common.Page; -import io.quarkus.panache.common.Range; -import io.quarkus.panache.common.exception.PanacheQueryException; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; +@SuppressWarnings("unchecked") public class ReactivePanacheQueryImpl implements ReactivePanacheQuery { - private ReactiveMongoCollection collection; - private Bson mongoQuery; - private Bson sort; - private Bson projections; - - private Page page; - private Uni count; - - private Range range; - - private Collation collation; + private final CommonReactivePanacheQueryImpl delegate; ReactivePanacheQueryImpl(ReactiveMongoCollection collection, Bson mongoQuery, Bson sort) { - this.collection = collection; - this.mongoQuery = mongoQuery; - this.sort = sort; + this.delegate = new CommonReactivePanacheQueryImpl<>(collection, mongoQuery, sort); } - private ReactivePanacheQueryImpl(ReactivePanacheQueryImpl previousQuery, Bson projections, Class type) { - this.collection = previousQuery.collection.withDocumentClass(type); - this.mongoQuery = previousQuery.mongoQuery; - this.sort = previousQuery.sort; - this.projections = projections; - this.page = previousQuery.page; - this.count = previousQuery.count; - this.range = previousQuery.range; - this.collation = previousQuery.collation; + private ReactivePanacheQueryImpl(CommonReactivePanacheQueryImpl delegate) { + this.delegate = delegate; } - // Builder - @Override public ReactivePanacheQuery project(Class type) { - // collect field names from public fields and getters - Set fieldNames = MongoPropertyUtil.collectFields(type); - - // create the projection document - Document projections = new Document(); - for (String fieldName : fieldNames) { - projections.append(fieldName, 1); - } - - return new ReactivePanacheQueryImpl(this, projections, type); + return new ReactivePanacheQueryImpl<>(delegate.project(type)); } @Override @SuppressWarnings("unchecked") public ReactivePanacheQuery page(Page page) { - this.page = page; - this.range = null; // reset the range to be able to switch from range to page + delegate.page(page); return (ReactivePanacheQuery) this; } @@ -80,179 +44,92 @@ public ReactivePanacheQuery page(int pageIndex, int pageSi @Override public ReactivePanacheQuery nextPage() { - checkPagination(); - return page(page.next()); + delegate.nextPage(); + return (ReactivePanacheQuery) this; } @Override public ReactivePanacheQuery previousPage() { - checkPagination(); - return page(page.previous()); + delegate.previousPage(); + return (ReactivePanacheQuery) this; } @Override public ReactivePanacheQuery firstPage() { - checkPagination(); - return page(page.first()); + delegate.firstPage(); + return (ReactivePanacheQuery) this; } @Override public Uni> lastPage() { - checkPagination(); - return pageCount().map(pageCount -> page(page.index(pageCount - 1))); + Uni> uni = delegate.lastPage(); + return uni.map(q -> (ReactivePanacheQuery) this); } @Override public Uni hasNextPage() { - checkPagination(); - return pageCount().map(pageCount -> page.index < (pageCount - 1)); + return delegate.hasNextPage(); } @Override public boolean hasPreviousPage() { - checkPagination(); - return page.index > 0; + return delegate.hasPreviousPage(); } @Override public Uni pageCount() { - checkPagination(); - return count().map(count -> { - if (count == 0) - return 1; // a single page of zero results - return (int) Math.ceil((double) count / (double) page.size); - }); + return delegate.pageCount(); } @Override public Page page() { - checkPagination(); - return page; - } - - private void checkPagination() { - if (page == null) { - throw new UnsupportedOperationException( - "Cannot call a page related method, " - + "call page(Page) or page(int, int) to initiate pagination first"); - } - if (range != null) { - throw new UnsupportedOperationException("Cannot call a page related method in a ranged query, " + - "call page(Page) or page(int, int) to initiate pagination first"); - } + return delegate.page(); } @Override public ReactivePanacheQuery range(int startIndex, int lastIndex) { - this.range = Range.of(startIndex, lastIndex); - // reset the page to its default to be able to switch from page to range - this.page = null; + delegate.range(startIndex, lastIndex); return (ReactivePanacheQuery) this; } @Override public ReactivePanacheQuery withCollation(Collation collation) { - this.collation = collation; + delegate.withCollation(collation); return (ReactivePanacheQuery) this; } - // Results - @Override - @SuppressWarnings("unchecked") public Uni count() { - if (count == null) { - count = collection.countDocuments(mongoQuery); - } - return count; + return delegate.count(); } @Override - @SuppressWarnings("unchecked") public Uni> list() { - Multi results = stream(); - return results.collectItems().asList(); + return delegate.list(); } @Override - @SuppressWarnings("unchecked") public Multi stream() { - FindOptions options = buildOptions(); - return mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); + return delegate.stream(); } @Override public Uni firstResult() { - Uni> optionalEntity = firstResultOptional(); - return optionalEntity.map(optional -> optional.orElse(null)); + return delegate.firstResult(); } @Override public Uni> firstResultOptional() { - FindOptions options = buildOptions(1); - Multi results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); - return results.collectItems().first().map(o -> Optional.ofNullable(o)); + return delegate.firstResultOptional(); } @Override - @SuppressWarnings("unchecked") public Uni singleResult() { - FindOptions options = buildOptions(2); - Multi results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); - return results.collectItems().asList().map(list -> { - if (list.size() != 1) { - throw new PanacheQueryException("There should be only one result"); - } else { - return list.get(0); - } - }); + return delegate.singleResult(); } @Override public Uni> singleResultOptional() { - FindOptions options = buildOptions(2); - Multi results = mongoQuery == null ? collection.find(options) : collection.find(mongoQuery, options); - return results.collectItems().asList().map(list -> { - if (list.size() == 2) { - throw new PanacheQueryException("There should be no more than one result"); - } - return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); - }); - } - - private FindOptions buildOptions() { - FindOptions options = new FindOptions(); - options.sort(sort); - if (range != null) { - // range is 0 based, so we add 1 to the limit - options.skip(range.getStartIndex()).limit(range.getLastIndex() - range.getStartIndex() + 1); - } else if (page != null) { - options.skip(page.index * page.size).limit(page.size); - } - if (projections != null) { - options.projection(this.projections); - } - if (this.collation != null) { - options.collation(collation); - } - return options; - } - - private FindOptions buildOptions(int maxResults) { - FindOptions options = new FindOptions(); - options.sort(sort); - if (range != null) { - // range is 0 based, so we add 1 to the limit - options.skip(range.getStartIndex()); - } else if (page != null) { - options.skip(page.index * page.size); - } - if (projections != null) { - options.projection(this.projections); - } - if (this.collation != null) { - options.collation(collation); - } - return options.limit(maxResults); + return delegate.singleResultOptional(); } } diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/JavaMongoOperations.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/JavaMongoOperations.java new file mode 100644 index 0000000000000..94d48e4ddb9a0 --- /dev/null +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/JavaMongoOperations.java @@ -0,0 +1,33 @@ +package io.quarkus.mongodb.panache.runtime; + +import java.util.List; +import java.util.stream.Stream; + +import org.bson.Document; + +import com.mongodb.client.MongoCollection; + +import io.quarkus.mongodb.panache.PanacheQuery; +import io.quarkus.mongodb.panache.PanacheUpdate; + +public class JavaMongoOperations extends MongoOperations, PanacheUpdate> { + @Override + protected PanacheQuery createQuery(MongoCollection collection, Document query, Document sortDoc) { + return new PanacheQueryImpl(collection, query, sortDoc); + } + + @Override + protected PanacheUpdate createUpdate(MongoCollection collection, Class entityClass, Document docUpdate) { + return new PanacheUpdateImpl(this, entityClass, docUpdate, collection); + } + + @Override + protected List list(PanacheQuery query) { + return query.list(); + } + + @Override + protected Stream stream(PanacheQuery query) { + return query.stream(); + } +} diff --git a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheQueryImpl.java b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheQueryImpl.java index 63bcc519a788a..bd32897123034 100644 --- a/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheQueryImpl.java +++ b/extensions/panache/mongodb-panache/runtime/src/main/java/io/quarkus/mongodb/panache/runtime/PanacheQueryImpl.java @@ -1,252 +1,136 @@ package io.quarkus.mongodb.panache.runtime; -import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.stream.Stream; -import org.bson.Document; import org.bson.conversions.Bson; -import com.mongodb.client.FindIterable; import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoCursor; import com.mongodb.client.model.Collation; import io.quarkus.mongodb.panache.PanacheQuery; import io.quarkus.panache.common.Page; -import io.quarkus.panache.common.Range; -import io.quarkus.panache.common.exception.PanacheQueryException; public class PanacheQueryImpl implements PanacheQuery { - private MongoCollection collection; - private Bson mongoQuery; - private Bson sort; - private Bson projections; - - private Page page; - private Long count; - - private Range range; - - private Collation collation; + private final CommonPanacheQueryImpl delegate; PanacheQueryImpl(MongoCollection collection, Bson mongoQuery, Bson sort) { - this.collection = collection; - this.mongoQuery = mongoQuery; - this.sort = sort; + this.delegate = new CommonPanacheQueryImpl<>(collection, mongoQuery, sort); } - private PanacheQueryImpl(PanacheQueryImpl previousQuery, Bson projections, Class documentClass) { - this.collection = previousQuery.collection.withDocumentClass(documentClass); - this.mongoQuery = previousQuery.mongoQuery; - this.sort = previousQuery.sort; - this.projections = projections; - this.page = previousQuery.page; - this.count = previousQuery.count; - this.range = previousQuery.range; - this.collation = previousQuery.collation; + private PanacheQueryImpl(CommonPanacheQueryImpl delegate) { + this.delegate = delegate; } - // Builder - @Override public PanacheQuery project(Class type) { - // collect field names from public fields and getters - Set fieldNames = MongoPropertyUtil.collectFields(type); - - // create the projection document - Document projections = new Document(); - for (String fieldName : fieldNames) { - projections.append(fieldName, 1); - } - - return new PanacheQueryImpl<>(this, projections, type); + return new PanacheQueryImpl<>(delegate.project(type)); } @Override @SuppressWarnings("unchecked") public PanacheQuery page(Page page) { - this.page = page; - this.range = null; // reset the range to be able to switch from range to page + delegate.page(page); return (PanacheQuery) this; } @Override public PanacheQuery page(int pageIndex, int pageSize) { - return page(Page.of(pageIndex, pageSize)); + delegate.page(Page.of(pageIndex, pageSize)); + return (PanacheQuery) this; } @Override public PanacheQuery nextPage() { - checkPagination(); - return page(page.next()); + delegate.nextPage(); + return (PanacheQuery) this; } @Override public PanacheQuery previousPage() { - checkPagination(); - return page(page.previous()); + delegate.previousPage(); + return (PanacheQuery) this; } @Override public PanacheQuery firstPage() { - checkPagination(); - return page(page.first()); + delegate.firstPage(); + return (PanacheQuery) this; } @Override public PanacheQuery lastPage() { - checkPagination(); - return page(page.index(pageCount() - 1)); + delegate.lastPage(); + return (PanacheQuery) this; } @Override public boolean hasNextPage() { - checkPagination(); - return page.index < (pageCount() - 1); + return delegate.hasNextPage(); } @Override public boolean hasPreviousPage() { - checkPagination(); - return page.index > 0; + return delegate.hasPreviousPage(); } @Override public int pageCount() { - checkPagination(); - long count = count(); - if (count == 0) - return 1; // a single page of zero results - return (int) Math.ceil((double) count / (double) page.size); + return delegate.pageCount(); } @Override public Page page() { - checkPagination(); - return page; - } - - private void checkPagination() { - if (page == null) { - throw new UnsupportedOperationException( - "Cannot call a page related method, " - + "call page(Page) or page(int, int) to initiate pagination first"); - } - if (range != null) { - throw new UnsupportedOperationException("Cannot call a page related method in a ranged query, " + - "call page(Page) or page(int, int) to initiate pagination first"); - } + return delegate.page(); } @Override public PanacheQuery range(int startIndex, int lastIndex) { - this.range = Range.of(startIndex, lastIndex); - // reset the page to its default to be able to switch from page to range - this.page = null; + delegate.range(startIndex, lastIndex); return (PanacheQuery) this; } @Override public PanacheQuery withCollation(Collation collation) { - this.collation = collation; + delegate.withCollation(collation); return (PanacheQuery) this; } // Results @Override - @SuppressWarnings("unchecked") public long count() { - if (count == null) { - count = collection.countDocuments(mongoQuery); - } - return count; + return delegate.count(); } @Override public List list() { - return list(null); - } - - @SuppressWarnings("unchecked") - private List list(Integer limit) { - List list = new ArrayList<>(); - FindIterable find = mongoQuery == null ? collection.find() : collection.find(mongoQuery); - if (this.projections != null) { - find.projection(projections); - } - if (this.collation != null) { - find.collation(collation); - } - manageOffsets(find, limit); - MongoCursor cursor = find.sort(sort).iterator(); - - try { - while (cursor.hasNext()) { - T entity = cursor.next(); - list.add(entity); - } - } finally { - cursor.close(); - } - return list; + return delegate.list(); } @Override - @SuppressWarnings("unchecked") public Stream stream() { - return (Stream) list().stream(); + return delegate.stream(); } @Override public T firstResult() { - List list = list(1); - return list.isEmpty() ? null : list.get(0); + return delegate.firstResult(); } @Override public Optional firstResultOptional() { - return Optional.ofNullable(firstResult()); + return delegate.firstResultOptional(); } @Override public T singleResult() { - List list = list(2); - if (list.size() != 1) { - throw new PanacheQueryException("There should be only one result"); - } - - return list.get(0); + return delegate.singleResult(); } @Override public Optional singleResultOptional() { - List list = list(2); - if (list.size() > 1) { - throw new PanacheQueryException("There should be no more than one result"); - } - - return list.isEmpty() ? Optional.empty() : Optional.of(list.get(0)); - } - - private void manageOffsets(FindIterable find, Integer limit) { - if (range != null) { - find.skip(range.getStartIndex()); - if (limit == null) { - // range is 0 based, so we add 1 to the limit - find.limit(range.getLastIndex() - range.getStartIndex() + 1); - } - } else if (page != null) { - find.skip(page.index * page.size); - if (limit == null) { - find.limit(page.size); - } - } - if (limit != null) { - find.limit(limit); - } + return delegate.singleResultOptional(); } } diff --git a/extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/runtime/MongoOperationsTest.java b/extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/runtime/MongoOperationsTest.java index 03cefbed12cbe..2016dc4e05cde 100644 --- a/extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/runtime/MongoOperationsTest.java +++ b/extensions/panache/mongodb-panache/runtime/src/test/java/io/quarkus/mongodb/panache/runtime/MongoOperationsTest.java @@ -22,6 +22,8 @@ class MongoOperationsTest { + private final MongoOperations operations = new JavaMongoOperations(); + private static class DemoObj { public String field; public boolean isOk; @@ -41,32 +43,32 @@ static void setupFieldReplacement() { @Test public void testBindShorthandFilter() { - String query = MongoOperations.bindFilter(Object.class, "field", new Object[] { "a value" }); + String query = operations.bindFilter(Object.class, "field", new Object[] { "a value" }); assertEquals("{'field':'a value'}", query); - query = MongoOperations.bindFilter(Object.class, "field", new Object[] { true }); + query = operations.bindFilter(Object.class, "field", new Object[] { true }); assertEquals("{'field':true}", query); - query = MongoOperations.bindFilter(Object.class, "field", new Object[] { LocalDate.of(2019, 3, 4) }); + query = operations.bindFilter(Object.class, "field", new Object[] { LocalDate.of(2019, 3, 4) }); assertEquals("{'field':ISODate('2019-03-04')}", query); - query = MongoOperations.bindFilter(Object.class, "field", new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1) }); + query = operations.bindFilter(Object.class, "field", new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1) }); assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query); - query = MongoOperations.bindFilter(Object.class, "field", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "field", + query = operations.bindFilter(Object.class, "field", new Object[] { toDate(LocalDateTime.of(2019, 3, 4, 1, 1, 1)) }); assertEquals("{'field':ISODate('2019-03-04T01:01:01.000Z')}", query); - query = MongoOperations.bindFilter(Object.class, "field", + query = operations.bindFilter(Object.class, "field", new Object[] { UUID.fromString("7f000101-7370-1f68-8173-70afa71b0000") }); assertEquals("{'field':UUID('7f000101-7370-1f68-8173-70afa71b0000')}", query); //test field replacement - query = MongoOperations.bindFilter(DemoObj.class, "property", new Object[] { "a value" }); + query = operations.bindFilter(DemoObj.class, "property", new Object[] { "a value" }); assertEquals("{'value':'a value'}", query); } @@ -76,53 +78,53 @@ private Object toDate(LocalDateTime of) { @Test public void testBindNativeFilterByIndex() { - String query = MongoOperations.bindFilter(DemoObj.class, "{'field': ?1}", new Object[] { "a value" }); + String query = operations.bindFilter(DemoObj.class, "{'field': ?1}", new Object[] { "a value" }); assertEquals("{'field': 'a value'}", query); - query = MongoOperations.bindFilter(DemoObj.class, "{'field.sub': ?1}", new Object[] { "a value" }); + query = operations.bindFilter(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 = MongoOperations.bindFilter(DemoObj.class, "{'property': ?1}", new Object[] { "a value" }); + query = operations.bindFilter(DemoObj.class, "{'property': ?1}", new Object[] { "a value" }); assertEquals("{'property': 'a value'}", query); - query = MongoOperations.bindFilter(Object.class, "{'field': ?1}", + query = operations.bindFilter(Object.class, "{'field': ?1}", new Object[] { LocalDate.of(2019, 3, 4) }); assertEquals("{'field': ISODate('2019-03-04')}", query); - query = MongoOperations.bindFilter(Object.class, "{'field': ?1}", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "{'field': ?1}", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "{'field': ?1}", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "{'field': ?1}", + query = operations.bindFilter(Object.class, "{'field': ?1}", new Object[] { UUID.fromString("7f000101-7370-1f68-8173-70afa71b0000") }); assertEquals("{'field': UUID('7f000101-7370-1f68-8173-70afa71b0000')}", query); - query = MongoOperations.bindFilter(Object.class, "{'field': ?1, 'isOk': ?2}", new Object[] { "a value", true }); + query = operations.bindFilter(Object.class, "{'field': ?1, 'isOk': ?2}", new Object[] { "a value", true }); assertEquals("{'field': 'a value', 'isOk': true}", query); //queries related to '$in' operator List list = Arrays.asList("f1", "f2"); - query = MongoOperations.bindFilter(DemoObj.class, "{ field: { '$in': [?1] } }", new Object[] { list }); + query = operations.bindFilter(DemoObj.class, "{ field: { '$in': [?1] } }", new Object[] { list }); assertEquals("{ field: { '$in': ['f1', 'f2'] } }", query); - query = MongoOperations.bindFilter(DemoObj.class, "{ field: { '$in': [?1] }, isOk: ?2 }", new Object[] { list, true }); + query = operations.bindFilter(DemoObj.class, "{ field: { '$in': [?1] }, isOk: ?2 }", new Object[] { list, true }); assertEquals("{ field: { '$in': ['f1', 'f2'] }, isOk: true }", query); - query = MongoOperations.bindFilter(DemoObj.class, + query = operations.bindFilter(DemoObj.class, "{ field: { '$in': [?1] }, $or: [ {'property': ?2}, {'property': ?3} ] }", new Object[] { list, "jpg", "gif" }); assertEquals("{ field: { '$in': ['f1', 'f2'] }, $or: [ {'property': 'jpg'}, {'property': 'gif'} ] }", query); - query = MongoOperations.bindFilter(DemoObj.class, + query = operations.bindFilter(DemoObj.class, "{ field: { '$in': [?1] }, isOk: ?2, $or: [ {'property': ?3}, {'property': ?4} ] }", new Object[] { list, true, "jpg", "gif" }); assertEquals("{ field: { '$in': ['f1', 'f2'] }, isOk: true, $or: [ {'property': 'jpg'}, {'property': 'gif'} ] }", @@ -131,59 +133,59 @@ public void testBindNativeFilterByIndex() { @Test public void testBindNativeFilterByName() { - String query = MongoOperations.bindFilter(Object.class, "{'field': :field}", + String query = operations.bindFilter(Object.class, "{'field': :field}", Parameters.with("field", "a value").map()); assertEquals("{'field': 'a value'}", query); - query = MongoOperations.bindFilter(Object.class, "{'field.sub': :field}", + query = operations.bindFilter(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 = MongoOperations.bindFilter(DemoObj.class, "{'property': :field}", + query = operations.bindFilter(DemoObj.class, "{'property': :field}", Parameters.with("field", "a value").map()); assertEquals("{'property': 'a value'}", query); - query = MongoOperations.bindFilter(Object.class, "{'field': :field}", + query = operations.bindFilter(Object.class, "{'field': :field}", Parameters.with("field", LocalDate.of(2019, 3, 4)).map()); assertEquals("{'field': ISODate('2019-03-04')}", query); - query = MongoOperations.bindFilter(Object.class, "{'field': :field}", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "{'field': :field}", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "{'field': :field}", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "{'field': :field}", + query = operations.bindFilter(Object.class, "{'field': :field}", Parameters.with("field", UUID.fromString("7f000101-7370-1f68-8173-70afa71b0000")).map()); assertEquals("{'field': UUID('7f000101-7370-1f68-8173-70afa71b0000')}", query); - query = MongoOperations.bindFilter(Object.class, "{'field': :field, 'isOk': :isOk}", + query = operations.bindFilter(Object.class, "{'field': :field, 'isOk': :isOk}", Parameters.with("field", "a value").and("isOk", true).map()); assertEquals("{'field': 'a value', 'isOk': true}", query); //queries related to '$in' operator List ids = Arrays.asList("f1", "f2"); - query = MongoOperations.bindFilter(DemoObj.class, "{ field: { '$in': [:fields] } }", + query = operations.bindFilter(DemoObj.class, "{ field: { '$in': [:fields] } }", Parameters.with("fields", ids).map()); assertEquals("{ field: { '$in': ['f1', 'f2'] } }", query); - query = MongoOperations.bindFilter(DemoObj.class, "{ field: { '$in': [:fields] }, isOk: :isOk }", + query = operations.bindFilter(DemoObj.class, "{ field: { '$in': [:fields] }, isOk: :isOk }", Parameters.with("fields", ids).and("isOk", true).map()); assertEquals("{ field: { '$in': ['f1', 'f2'] }, isOk: true }", query); - query = MongoOperations.bindFilter(DemoObj.class, + query = operations.bindFilter(DemoObj.class, "{ field: { '$in': [:fields] }, $or: [ {'property': :p1}, {'property': :p2} ] }", Parameters.with("fields", ids).and("p1", "jpg").and("p2", "gif").map()); assertEquals("{ field: { '$in': ['f1', 'f2'] }, $or: [ {'property': 'jpg'}, {'property': 'gif'} ] }", query); - query = MongoOperations.bindFilter(DemoObj.class, + query = operations.bindFilter(DemoObj.class, "{ field: { '$in': [:fields] }, isOk: :isOk, $or: [ {'property': :p1}, {'property': :p2} ] }", Parameters.with("fields", ids) .and("isOk", true) @@ -195,74 +197,74 @@ public void testBindNativeFilterByName() { @Test public void testBindEnhancedFilterByIndex() { - String query = MongoOperations.bindFilter(Object.class, "field = ?1", new Object[] { "a value" }); + String query = operations.bindFilter(Object.class, "field = ?1", new Object[] { "a value" }); assertEquals("{'field':'a value'}", query); - query = MongoOperations.bindFilter(Object.class, "{'field.sub': :field}", + query = operations.bindFilter(Object.class, "{'field.sub': :field}", Parameters.with("field", "a value").map()); assertEquals("{'field.sub': 'a value'}", query); //test field replacement - query = MongoOperations.bindFilter(DemoObj.class, "property = ?1", new Object[] { "a value" }); + query = operations.bindFilter(DemoObj.class, "property = ?1", new Object[] { "a value" }); assertEquals("{'value':'a value'}", query); - query = MongoOperations.bindFilter(Object.class, "field = ?1", new Object[] { LocalDate.of(2019, 3, 4) }); + query = operations.bindFilter(Object.class, "field = ?1", new Object[] { LocalDate.of(2019, 3, 4) }); assertEquals("{'field':ISODate('2019-03-04')}", query); - query = MongoOperations.bindFilter(Object.class, "field = ?1", new Object[] { LocalDateTime.of(2019, 3, 4, 1, 1, 1) }); + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "field = ?1", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "field = ?1", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "field = ?1", + query = operations.bindFilter(Object.class, "field = ?1", new Object[] { UUID.fromString("7f000101-7370-1f68-8173-70afa71b0000") }); assertEquals("{'field':UUID('7f000101-7370-1f68-8173-70afa71b0000')}", query); - query = MongoOperations.bindFilter(Object.class, "field = ?1 and isOk = ?2", new Object[] { "a value", true }); + query = operations.bindFilter(Object.class, "field = ?1 and isOk = ?2", new Object[] { "a value", true }); assertEquals("{'field':'a value','isOk':true}", query); - query = MongoOperations.bindFilter(Object.class, "field = ?1 or isOk = ?2", new Object[] { "a value", true }); + query = operations.bindFilter(Object.class, "field = ?1 or isOk = ?2", new Object[] { "a value", true }); assertEquals("{'$or':[{'field':'a value'},{'isOk':true}]}", query); - query = MongoOperations.bindFilter(Object.class, "count >= ?1 and count < ?2", new Object[] { 5, 10 }); + query = operations.bindFilter(Object.class, "count >= ?1 and count < ?2", new Object[] { 5, 10 }); assertEquals("{'count':{'$gte':5},'count':{'$lt':10}}", query); - query = MongoOperations.bindFilter(Object.class, "field != ?1", new Object[] { "a value" }); + query = operations.bindFilter(Object.class, "field != ?1", new Object[] { "a value" }); assertEquals("{'field':{'$ne':'a value'}}", query); - query = MongoOperations.bindFilter(Object.class, "field like ?1", new Object[] { "a value" }); + query = operations.bindFilter(Object.class, "field like ?1", new Object[] { "a value" }); assertEquals("{'field':{'$regex':'a value'}}", query); - query = MongoOperations.bindFilter(Object.class, "field is not null", new Object[] {}); + query = operations.bindFilter(Object.class, "field is not null", new Object[] {}); assertEquals("{'field':{'$exists':true}}", query); - query = MongoOperations.bindFilter(Object.class, "field is null", new Object[] {}); + query = operations.bindFilter(Object.class, "field is null", new Object[] {}); assertEquals("{'field':{'$exists':false}}", query); // test with hardcoded value - query = MongoOperations.bindFilter(Object.class, "field = 'some hardcoded value'", new Object[] {}); + query = operations.bindFilter(Object.class, "field = 'some hardcoded value'", new Object[] {}); assertEquals("{'field':'some hardcoded value'}", query); //queries related to '$in' operator List list = Arrays.asList("f1", "f2"); - query = MongoOperations.bindFilter(DemoObj.class, "field in ?1", new Object[] { list }); + query = operations.bindFilter(DemoObj.class, "field in ?1", new Object[] { list }); assertEquals("{'field':{'$in':['f1', 'f2']}}", query); - query = MongoOperations.bindFilter(DemoObj.class, "field in ?1 and isOk = ?2", new Object[] { list, true }); + query = operations.bindFilter(DemoObj.class, "field in ?1 and isOk = ?2", new Object[] { list, true }); assertEquals("{'field':{'$in':['f1', 'f2']},'isOk':true}", query); - query = MongoOperations.bindFilter(DemoObj.class, + query = operations.bindFilter(DemoObj.class, "field in ?1 and property = ?2 or property = ?3", new Object[] { list, "jpg", "gif" }); assertEquals("{'field':{'$in':['f1', 'f2']},'$or':[{'value':'jpg'},{'value':'gif'}]}", query); - query = MongoOperations.bindFilter(DemoObj.class, + query = operations.bindFilter(DemoObj.class, "field in ?1 and isOk = ?2 and property = ?3 or property = ?4", new Object[] { list, true, "jpg", "gif" }); assertEquals("{'field':{'$in':['f1', 'f2']},'isOk':true,'$or':[{'value':'jpg'},{'value':'gif'}]}", query); @@ -270,75 +272,75 @@ public void testBindEnhancedFilterByIndex() { @Test public void testBindEnhancedFilterByName() { - String query = MongoOperations.bindFilter(Object.class, "field = :field", + String query = operations.bindFilter(Object.class, "field = :field", Parameters.with("field", "a value").map()); assertEquals("{'field':'a value'}", query); - query = MongoOperations.bindFilter(Object.class, "field.sub = :field", + query = operations.bindFilter(Object.class, "field.sub = :field", Parameters.with("field", "a value").map()); assertEquals("{'field.sub':'a value'}", query); //test field replacement - query = MongoOperations.bindFilter(DemoObj.class, "property = :field", + query = operations.bindFilter(DemoObj.class, "property = :field", Parameters.with("field", "a value").map()); assertEquals("{'value':'a value'}", query); - query = MongoOperations.bindFilter(Object.class, "field = :field", + query = operations.bindFilter(Object.class, "field = :field", Parameters.with("field", LocalDate.of(2019, 3, 4)).map()); assertEquals("{'field':ISODate('2019-03-04')}", query); - query = MongoOperations.bindFilter(Object.class, "field = :field", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "field = :field", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "field = :field", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "field = :field", + query = operations.bindFilter(Object.class, "field = :field", Parameters.with("field", UUID.fromString("7f000101-7370-1f68-8173-70afa71b0000")).map()); assertEquals("{'field':UUID('7f000101-7370-1f68-8173-70afa71b0000')}", query); - query = MongoOperations.bindFilter(Object.class, "field = :field and isOk = :isOk", + query = operations.bindFilter(Object.class, "field = :field and isOk = :isOk", Parameters.with("field", "a value").and("isOk", true).map()); assertEquals("{'field':'a value','isOk':true}", query); - query = MongoOperations.bindFilter(Object.class, "field = :field or isOk = :isOk", + query = operations.bindFilter(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 = MongoOperations.bindFilter(Object.class, "count > :lower and count <= :upper", + query = operations.bindFilter(Object.class, "count > :lower and count <= :upper", Parameters.with("lower", 5).and("upper", 10).map()); assertEquals("{'count':{'$gt':5},'count':{'$lte':10}}", query); - query = MongoOperations.bindFilter(Object.class, "field != :field", + query = operations.bindFilter(Object.class, "field != :field", Parameters.with("field", "a value").map()); assertEquals("{'field':{'$ne':'a value'}}", query); - query = MongoOperations.bindFilter(Object.class, "field like :field", + query = operations.bindFilter(Object.class, "field like :field", Parameters.with("field", "a value").map()); assertEquals("{'field':{'$regex':'a value'}}", query); //queries related to '$in' operator List list = Arrays.asList("f1", "f2"); - query = MongoOperations.bindFilter(DemoObj.class, "field in :fields", + query = operations.bindFilter(DemoObj.class, "field in :fields", Parameters.with("fields", list).map()); assertEquals("{'field':{'$in':['f1', 'f2']}}", query); - query = MongoOperations.bindFilter(DemoObj.class, "field in :fields and isOk = :isOk", + query = operations.bindFilter(DemoObj.class, "field in :fields and isOk = :isOk", Parameters.with("fields", list).and("isOk", true).map()); assertEquals("{'field':{'$in':['f1', 'f2']},'isOk':true}", query); - query = MongoOperations.bindFilter(DemoObj.class, + query = operations.bindFilter(DemoObj.class, "field in :fields and property = :p1 or property = :p2", Parameters.with("fields", list).and("p1", "jpg").and("p2", "gif").map()); assertEquals("{'field':{'$in':['f1', 'f2']},'$or':[{'value':'jpg'},{'value':'gif'}]}", query); - query = MongoOperations.bindFilter(DemoObj.class, + query = operations.bindFilter(DemoObj.class, "field in :fields and isOk = :isOk and property = :p1 or property = :p2", Parameters.with("fields", list) .and("isOk", true) @@ -350,33 +352,33 @@ public void testBindEnhancedFilterByName() { @Test public void testBindUpdate() { // native update by index without $set - String update = MongoOperations.bindUpdate(DemoObj.class, "{'field': ?1}", new Object[] { "a value" }); + String update = operations.bindUpdate(DemoObj.class, "{'field': ?1}", new Object[] { "a value" }); assertEquals("{'$set':{'field': 'a value'}}", update); // native update by name without $set - update = MongoOperations.bindUpdate(Object.class, "{'field': :field}", + update = operations.bindUpdate(Object.class, "{'field': :field}", Parameters.with("field", "a value").map()); assertEquals("{'$set':{'field': 'a value'}}", update); // native update by index with $set - update = MongoOperations.bindUpdate(DemoObj.class, "{'$set':{'field': ?1}}", new Object[] { "a value" }); + update = operations.bindUpdate(DemoObj.class, "{'$set':{'field': ?1}}", new Object[] { "a value" }); assertEquals("{'$set':{'field': 'a value'}}", update); // native update by name with $set - update = MongoOperations.bindUpdate(Object.class, "{'$set':{'field': :field}}", + update = operations.bindUpdate(Object.class, "{'$set':{'field': :field}}", Parameters.with("field", "a value").map()); assertEquals("{'$set':{'field': 'a value'}}", update); // shortand update - update = MongoOperations.bindUpdate(Object.class, "field", new Object[] { "a value" }); + update = operations.bindUpdate(Object.class, "field", new Object[] { "a value" }); assertEquals("{'$set':{'field':'a value'}}", update); // enhanced update by index - update = MongoOperations.bindUpdate(Object.class, "field = ?1", new Object[] { "a value" }); + update = operations.bindUpdate(Object.class, "field = ?1", new Object[] { "a value" }); assertEquals("{'$set':{'field':'a value'}}", update); // enhanced update by name - update = MongoOperations.bindUpdate(Object.class, "field = :field", + update = operations.bindUpdate(Object.class, "field = :field", Parameters.with("field", "a value").map()); assertEquals("{'$set':{'field':'a value'}}", update); } diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java index 7ca3128a8d780..9ad0cc67daea2 100644 --- a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheEntityEnhancer.java @@ -1,64 +1,34 @@ package io.quarkus.panache.common.deployment; -import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.function.BiFunction; -import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; -import org.jboss.jandex.FieldInfo; import org.jboss.jandex.IndexView; -import org.jboss.jandex.MethodInfo; -import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import io.quarkus.deployment.util.AsmUtil; -import io.quarkus.gizmo.Gizmo; -import io.quarkus.panache.common.Parameters; -import io.quarkus.panache.common.Sort; -import io.quarkus.panache.common.deployment.EntityField.EntityFieldAnnotation; import io.quarkus.panache.common.impl.GenerateBridge; public abstract class PanacheEntityEnhancer> implements BiFunction { - public final static DotName DOTNAME_GENERATE_BRIDGE = DotName.createSimple(GenerateBridge.class.getName()); + public static final DotName DOTNAME_GENERATE_BRIDGE = DotName.createSimple(GenerateBridge.class.getName()); - public final static String SORT_NAME = Sort.class.getName(); - public final static String SORT_BINARY_NAME = SORT_NAME.replace('.', '/'); - public final static String SORT_SIGNATURE = "L" + SORT_BINARY_NAME + ";"; - - public final static String PARAMETERS_NAME = Parameters.class.getName(); - public final static String PARAMETERS_BINARY_NAME = PARAMETERS_NAME.replace('.', '/'); - public final static String PARAMETERS_SIGNATURE = "L" + PARAMETERS_BINARY_NAME + ";"; - - private static final String JAXB_ANNOTATION_PREFIX = "Ljavax/xml/bind/annotation/"; + public static final String JAXB_ANNOTATION_PREFIX = "Ljavax/xml/bind/annotation/"; private static final String JAXB_TRANSIENT_BINARY_NAME = "javax/xml/bind/annotation/XmlTransient"; - private static final String JAXB_TRANSIENT_SIGNATURE = "L" + JAXB_TRANSIENT_BINARY_NAME + ";"; + public static final String JAXB_TRANSIENT_SIGNATURE = "L" + JAXB_TRANSIENT_BINARY_NAME + ";"; private static final String JSON_PROPERTY_BINARY_NAME = "com/fasterxml/jackson/annotation/JsonProperty"; - private static final String JSON_PROPERTY_SIGNATURE = "L" + JSON_PROPERTY_BINARY_NAME + ";"; + public static final String JSON_PROPERTY_SIGNATURE = "L" + JSON_PROPERTY_BINARY_NAME + ";"; - private static final DotName JSON_IGNORE_DOT_NAME = DotName.createSimple("com.fasterxml.jackson.annotation.JsonIgnore"); + public static final DotName JSON_IGNORE_DOT_NAME = DotName.createSimple("com.fasterxml.jackson.annotation.JsonIgnore"); protected MetamodelType modelInfo; - protected final ClassInfo panacheEntityBaseClassInfo; protected final IndexView indexView; protected final List methodCustomizers; - public PanacheEntityEnhancer(IndexView index, DotName panacheEntityBaseName, - List methodCustomizers) { - this.panacheEntityBaseClassInfo = index.getClassByName(panacheEntityBaseName); + public PanacheEntityEnhancer(IndexView index, List methodCustomizers) { this.indexView = index; this.methodCustomizers = methodCustomizers; } @@ -66,260 +36,6 @@ public PanacheEntityEnhancer(IndexView index, DotName panacheEntityBaseName, @Override public abstract ClassVisitor apply(String className, ClassVisitor outputClassVisitor); - public abstract static class PanacheEntityClassVisitor extends ClassVisitor { - - protected Type thisClass; - protected Map fields; - // set of name + "/" + descriptor - private Set userMethods = new HashSet<>(); - private MetamodelInfo modelInfo; - private ClassInfo panacheEntityBaseClassInfo; - protected ClassInfo entityInfo; - protected List methodCustomizers; - - public PanacheEntityClassVisitor(String className, ClassVisitor outputClassVisitor, - MetamodelInfo> modelInfo, - ClassInfo panacheEntityBaseClassInfo, - ClassInfo entityInfo, - List methodCustomizers) { - super(Gizmo.ASM_API_VERSION, outputClassVisitor); - thisClass = Type.getType("L" + className.replace('.', '/') + ";"); - this.modelInfo = modelInfo; - EntityModel entityModel = modelInfo.getEntityModel(className); - fields = entityModel != null ? entityModel.fields : null; - this.panacheEntityBaseClassInfo = panacheEntityBaseClassInfo; - this.entityInfo = entityInfo; - this.methodCustomizers = methodCustomizers; - } - - @Override - public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { - EntityField ef = fields.get(name); - if (ef == null) { - return super.visitField(access, name, descriptor, signature, value); - } - //we make the fields protected - //so any errors are visible immediately, rather than data just being lost - - FieldVisitor superVisitor; - if (name.equals("id")) { - superVisitor = super.visitField(access, name, descriptor, signature, value); - } else { - superVisitor = super.visitField((access | Modifier.PROTECTED) & ~(Modifier.PRIVATE | Modifier.PUBLIC), - name, descriptor, signature, value); - } - ef.signature = signature; - // if we have a mapped field, let's add some annotations - return new FieldVisitor(Gizmo.ASM_API_VERSION, superVisitor) { - private Set descriptors = new HashSet<>(); - - @Override - public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { - descriptors.add(descriptor); - if (!descriptor.startsWith(JAXB_ANNOTATION_PREFIX)) { - return super.visitAnnotation(descriptor, visible); - } else { - // Save off JAX-B annotations on the field so they can be applied to the generated getter later - EntityFieldAnnotation efAnno = new EntityFieldAnnotation(descriptor); - ef.annotations.add(efAnno); - return new PanacheMovingAnnotationVisitor(efAnno); - } - } - - @Override - public void visitEnd() { - // Add the @JaxbTransient property to the field so that JAXB prefers the generated getter (otherwise JAXB complains about - // having a field and property both with the same name) - // JSONB will already use the getter so we're good - // Note: we don't need to check if we already have @XmlTransient in the descriptors because if we did, we moved it to the getter - // so we can't have any duplicate - super.visitAnnotation(JAXB_TRANSIENT_SIGNATURE, true); - super.visitEnd(); - } - }; - } - - @Override - public MethodVisitor visitMethod(int access, String methodName, String descriptor, String signature, - String[] exceptions) { - userMethods.add(methodName + "/" + descriptor); - MethodVisitor superVisitor = super.visitMethod(access, methodName, descriptor, signature, exceptions); - if (Modifier.isStatic(access) - && Modifier.isPublic(access) - && (access & Opcodes.ACC_SYNTHETIC) == 0 - && !methodCustomizers.isEmpty()) { - org.jboss.jandex.Type[] argTypes = AsmUtil.getParameterTypes(descriptor); - MethodInfo method = this.entityInfo.method(methodName, argTypes); - if (method == null) { - throw new IllegalStateException( - "Could not find indexed method: " + thisClass + "." + methodName + " with descriptor " + descriptor - + " and arg types " + Arrays.toString(argTypes)); - } - superVisitor = new PanacheMethodCustomizerVisitor(superVisitor, method, thisClass, methodCustomizers); - } - return new PanacheFieldAccessMethodVisitor(superVisitor, thisClass.getInternalName(), methodName, descriptor, - modelInfo); - } - - @Override - public void visitEnd() { - // FIXME: generate default constructor - - for (MethodInfo method : panacheEntityBaseClassInfo.methods()) { - // Do not generate a method that already exists - String descriptor = AsmUtil.getDescriptor(method, name -> null); - if (!userMethods.contains(method.name() + "/" + descriptor)) { - AnnotationInstance bridge = method.annotation(DOTNAME_GENERATE_BRIDGE); - if (bridge != null) { - generateMethod(method, bridge.value("targetReturnTypeErased")); - } - } - } - - generateAccessors(); - - super.visitEnd(); - } - - private void generateMethod(MethodInfo method, AnnotationValue targetReturnTypeErased) { - String descriptor = AsmUtil.getDescriptor(method, name -> null); - String signature = AsmUtil.getSignature(method, name -> null); - List parameters = method.parameters(); - String castTo = null; - if (targetReturnTypeErased != null && targetReturnTypeErased.asBoolean()) { - castTo = method.returnType().name().toString('/'); - } - - MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, - method.name(), - descriptor, - signature, - null); - for (int i = 0; i < parameters.size(); i++) { - mv.visitParameter(method.parameterName(i), 0 /* modifiers */); - } - mv.visitCode(); - for (PanacheMethodCustomizer customizer : methodCustomizers) { - customizer.customize(thisClass, method, mv); - } - // inject model - injectModel(mv); - for (int i = 0; i < parameters.size(); i++) { - mv.visitIntInsn(Opcodes.ALOAD, i); - } - // inject Class - String forwardingDescriptor = "(" + getModelDescriptor() + descriptor.substring(1); - if (castTo != null) { - // return type is erased to Object - int lastParen = forwardingDescriptor.lastIndexOf(')'); - forwardingDescriptor = forwardingDescriptor.substring(0, lastParen + 1) + "Ljava/lang/Object;"; - } - mv.visitMethodInsn(Opcodes.INVOKESTATIC, - getPanacheOperationsBinaryName(), - method.name(), - forwardingDescriptor, false); - if (castTo != null) - mv.visitTypeInsn(Opcodes.CHECKCAST, castTo); - String returnTypeDescriptor = descriptor.substring(descriptor.lastIndexOf(")") + 1); - mv.visitInsn(AsmUtil.getReturnInstruction(returnTypeDescriptor)); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - - protected abstract String getModelDescriptor(); - - protected abstract String getPanacheOperationsBinaryName(); - - protected abstract void injectModel(MethodVisitor mv); - - protected void generateAccessors() { - if (fields == null) - return; - for (EntityField field : fields.values()) { - // Getter - String getterName = field.getGetterName(); - String getterDescriptor = "()" + field.descriptor; - if (!userMethods.contains(getterName + "/" + getterDescriptor)) { - MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, - getterName, getterDescriptor, field.signature == null ? null : "()" + field.signature, null); - mv.visitCode(); - mv.visitIntInsn(Opcodes.ALOAD, 0); - generateAccessorGetField(mv, field); - int returnCode = AsmUtil.getReturnInstruction(field.descriptor); - mv.visitInsn(returnCode); - mv.visitMaxs(0, 0); - // Apply JAX-B annotations that are being transferred from the field - for (EntityFieldAnnotation anno : field.annotations) { - anno.writeToVisitor(mv); - } - // Add an explicit Jackson annotation so that the entire property is not ignored due to having @XmlTransient - // on the field - if (shouldAddJsonProperty(field)) { - mv.visitAnnotation(JSON_PROPERTY_SIGNATURE, true); - } - mv.visitEnd(); - } - - // Setter - String setterName = field.getSetterName(); - String setterDescriptor = "(" + field.descriptor + ")V"; - if (!userMethods.contains(setterName + "/" + setterDescriptor)) { - MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, - setterName, setterDescriptor, field.signature == null ? null : "(" + field.signature + ")V", null); - mv.visitCode(); - mv.visitIntInsn(Opcodes.ALOAD, 0); - int loadCode; - switch (field.descriptor) { - case "Z": - case "B": - case "C": - case "S": - case "I": - loadCode = Opcodes.ILOAD; - break; - case "J": - loadCode = Opcodes.LLOAD; - break; - case "F": - loadCode = Opcodes.FLOAD; - break; - case "D": - loadCode = Opcodes.DLOAD; - break; - default: - loadCode = Opcodes.ALOAD; - break; - } - mv.visitIntInsn(loadCode, 1); - generateAccessorSetField(mv, field); - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - } - } - - private boolean shouldAddJsonProperty(EntityField entityField) { - if (isAnnotatedWithJsonIgnore(entityField)) { - return false; - } - return !entityField.hasAnnotation(JSON_PROPERTY_SIGNATURE); - } - - private boolean isAnnotatedWithJsonIgnore(EntityField entityField) { - FieldInfo field = entityInfo.field(entityField.name); - if (field != null) { - return field.hasAnnotation(JSON_IGNORE_DOT_NAME); - } - - return false; - } - - protected abstract void generateAccessorSetField(MethodVisitor mv, EntityField field); - - protected abstract void generateAccessorGetField(MethodVisitor mv, EntityField field); - } - public abstract void collectFields(ClassInfo classInfo); public MetamodelType getModelInfo() { diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java index 1b83709d56759..ef55d00f18bc6 100644 --- a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheRepositoryEnhancer.java @@ -1,28 +1,12 @@ package io.quarkus.panache.common.deployment; import java.lang.reflect.Modifier; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.function.BiFunction; -import org.jboss.jandex.AnnotationInstance; -import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; -import org.jboss.jandex.MethodInfo; -import org.jboss.jandex.Type.Kind; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -import io.quarkus.deployment.util.AsmUtil; -import io.quarkus.deployment.util.JandexUtil; -import io.quarkus.gizmo.Gizmo; public abstract class PanacheRepositoryEnhancer implements BiFunction { @@ -37,237 +21,7 @@ public PanacheRepositoryEnhancer(IndexView index, DotName panacheRepositoryBaseN @Override public abstract ClassVisitor apply(String className, ClassVisitor outputClassVisitor); - public static abstract class PanacheRepositoryClassVisitor extends ClassVisitor { - - protected Type entityType; - protected String entitySignature; - protected String entityBinaryType; - protected String idSignature; - protected String idBinaryType; - protected String daoBinaryName; - protected ClassInfo daoClassInfo; - protected ClassInfo panacheRepositoryBaseClassInfo; - protected IndexView indexView; - protected Map typeArguments = new HashMap<>(); - // set of name + "/" + descriptor - protected Set userMethods = new HashSet<>(); - - public PanacheRepositoryClassVisitor(String className, ClassVisitor outputClassVisitor, - ClassInfo panacheRepositoryBaseClassInfo, IndexView indexView) { - super(Gizmo.ASM_API_VERSION, outputClassVisitor); - daoClassInfo = indexView.getClassByName(DotName.createSimple(className)); - daoBinaryName = className.replace('.', '/'); - this.panacheRepositoryBaseClassInfo = panacheRepositoryBaseClassInfo; - this.indexView = indexView; - } - - protected abstract DotName getPanacheRepositoryDotName(); - - protected abstract DotName getPanacheRepositoryBaseDotName(); - - protected abstract String getPanacheOperationsBinaryName(); - - protected abstract String getModelDescriptor(); - - protected abstract void injectModel(MethodVisitor mv); - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - super.visit(version, access, name, signature, superName, interfaces); - - final String repositoryClassName = name.replace('/', '.'); - - String[] foundTypeArguments = findEntityTypeArgumentsForPanacheRepository(indexView, repositoryClassName, - getPanacheRepositoryBaseDotName()); - - entityBinaryType = foundTypeArguments[0]; - entitySignature = "L" + entityBinaryType + ";"; - entityType = Type.getType(entitySignature); - idBinaryType = foundTypeArguments[1]; - idSignature = "L" + idBinaryType + ";"; - - typeArguments.put("Entity", entitySignature); - typeArguments.put("Id", idSignature); - } - - @Override - public MethodVisitor visitMethod(int access, String methodName, String descriptor, String signature, - String[] exceptions) { - userMethods.add(methodName + "/" + descriptor); - return super.visitMethod(access, methodName, descriptor, signature, exceptions); - } - - public static String[] findEntityTypeArgumentsForPanacheRepository(IndexView indexView, - String repositoryClassName, - DotName repositoryDotName) { - for (ClassInfo classInfo : indexView.getAllKnownImplementors(repositoryDotName)) { - if (repositoryClassName.equals(classInfo.name().toString())) { - return recursivelyFindEntityTypeArgumentsFromClass(indexView, classInfo.name(), repositoryDotName); - } - } - - return null; - } - - public static String[] recursivelyFindEntityTypeArgumentsFromClass(IndexView indexView, DotName clazz, - DotName repositoryDotName) { - if (clazz.equals(JandexUtil.DOTNAME_OBJECT)) { - return null; - } - - List typeParameters = JandexUtil - .resolveTypeParameters(clazz, repositoryDotName, indexView); - if (typeParameters.isEmpty()) - throw new IllegalStateException( - "Failed to find supertype " + repositoryDotName + " from entity class " + clazz); - org.jboss.jandex.Type entityType = typeParameters.get(0); - org.jboss.jandex.Type idType = typeParameters.get(1); - return new String[] { - entityType.name().toString().replace('.', '/'), - idType.name().toString().replace('.', '/') - }; - } - - @Override - public void visitEnd() { - for (MethodInfo method : panacheRepositoryBaseClassInfo.methods()) { - // Do not generate a method that already exists - String descriptor = AsmUtil.getDescriptor(method, name -> typeArguments.get(name)); - if (!userMethods.contains(method.name() + "/" + descriptor)) { - AnnotationInstance bridge = method.annotation(PanacheEntityEnhancer.DOTNAME_GENERATE_BRIDGE); - if (bridge != null) { - generateModelBridge(method, bridge.value("targetReturnTypeErased")); - if (needsJvmBridge(method)) { - generateJvmBridge(method); - } - } - } - } - super.visitEnd(); - } - - private boolean needsJvmBridge(MethodInfo method) { - if (needsJvmBridge(method.returnType())) - return true; - for (org.jboss.jandex.Type paramType : method.parameters()) { - if (needsJvmBridge(paramType)) - return true; - } - return false; - } - - private boolean needsJvmBridge(org.jboss.jandex.Type type) { - if (type.kind() == Kind.TYPE_VARIABLE) { - String typeParamName = type.asTypeVariable().identifier(); - return typeArguments.containsKey(typeParamName); - } - return false; - } - - private void generateJvmBridge(MethodInfo method) { - // get a bounds-erased descriptor - String descriptor = AsmUtil.getDescriptor(method, name -> null); - // make sure we need a bridge - if (!userMethods.contains(method.name() + "/" + descriptor)) { - MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE, - method.name(), - descriptor, - null, - null); - List parameters = method.parameters(); - for (int i = 0; i < parameters.size(); i++) { - mv.visitParameter(method.parameterName(i), 0 /* modifiers */); - } - mv.visitCode(); - // this - mv.visitIntInsn(Opcodes.ALOAD, 0); - // each param - for (int i = 0; i < parameters.size(); i++) { - org.jboss.jandex.Type paramType = parameters.get(i); - if (paramType.kind() == Kind.PRIMITIVE) - throw new IllegalStateException("BUG: Don't know how to generate JVM bridge method for " + method - + ": has primitive parameters"); - mv.visitIntInsn(Opcodes.ALOAD, i + 1); - if (paramType.kind() == Kind.TYPE_VARIABLE) { - String typeParamName = paramType.asTypeVariable().identifier(); - switch (typeParamName) { - case "Entity": - mv.visitTypeInsn(Opcodes.CHECKCAST, entityBinaryType); - break; - case "Id": - mv.visitTypeInsn(Opcodes.CHECKCAST, idBinaryType); - break; - } - } - } - - String targetDescriptor = AsmUtil.getDescriptor(method, name -> typeArguments.get(name)); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, - daoBinaryName, - method.name(), - targetDescriptor, false); - String targetReturnTypeDescriptor = targetDescriptor.substring(targetDescriptor.indexOf(')') + 1); - mv.visitInsn(AsmUtil.getReturnInstruction(targetReturnTypeDescriptor)); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - - } - - private void generateModelBridge(MethodInfo method, AnnotationValue targetReturnTypeErased) { - String descriptor = AsmUtil.getDescriptor(method, name -> typeArguments.get(name)); - // JpaOperations erases the Id type to Object - String descriptorForJpaOperations = AsmUtil.getDescriptor(method, - name -> name.equals("Entity") ? entitySignature : null); - String signature = AsmUtil.getSignature(method, name -> typeArguments.get(name)); - List parameters = method.parameters(); - - String castTo = null; - if (targetReturnTypeErased != null && targetReturnTypeErased.asBoolean()) { - org.jboss.jandex.Type type = method.returnType(); - if (type.kind() == Kind.TYPE_VARIABLE && - type.asTypeVariable().identifier().equals("Entity")) { - castTo = entityBinaryType; - } - if (castTo == null) - castTo = type.name().toString('/'); - } - - // Note: we can't use SYNTHETIC here because otherwise Mockito will never mock these methods - MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, - method.name(), - descriptor, - signature, - null); - for (int i = 0; i < parameters.size(); i++) { - mv.visitParameter(method.parameterName(i), 0 /* modifiers */); - } - mv.visitCode(); - injectModel(mv); - for (int i = 0; i < parameters.size(); i++) { - mv.visitIntInsn(Opcodes.ALOAD, i + 1); - } - // inject Class - String forwardingDescriptor = "(" + getModelDescriptor() + descriptorForJpaOperations.substring(1); - if (castTo != null) { - // return type is erased to Object - int lastParen = forwardingDescriptor.lastIndexOf(')'); - forwardingDescriptor = forwardingDescriptor.substring(0, lastParen + 1) + "Ljava/lang/Object;"; - } - mv.visitMethodInsn(Opcodes.INVOKESTATIC, - getPanacheOperationsBinaryName(), - method.name(), - forwardingDescriptor, false); - if (castTo != null) - mv.visitTypeInsn(Opcodes.CHECKCAST, castTo); - String returnTypeDescriptor = descriptor.substring(descriptor.lastIndexOf(")") + 1); - mv.visitInsn(AsmUtil.getReturnInstruction(returnTypeDescriptor)); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - } - - public static boolean skipRepository(ClassInfo classInfo) { + public boolean skipRepository(ClassInfo classInfo) { // we don't want to add methods to abstract/generic entities/repositories: they get added to bottom types // which can't be either return Modifier.isAbstract(classInfo.flags()) diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/visitors/PanacheEntityClassVisitor.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/visitors/PanacheEntityClassVisitor.java new file mode 100644 index 0000000000000..9713118aff299 --- /dev/null +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/visitors/PanacheEntityClassVisitor.java @@ -0,0 +1,293 @@ +package io.quarkus.panache.common.deployment.visitors; + +import static io.quarkus.panache.common.deployment.PanacheEntityEnhancer.JSON_IGNORE_DOT_NAME; +import static io.quarkus.panache.common.deployment.PanacheEntityEnhancer.JSON_PROPERTY_SIGNATURE; + +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.MethodInfo; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import io.quarkus.deployment.util.AsmUtil; +import io.quarkus.gizmo.Gizmo; +import io.quarkus.panache.common.deployment.EntityField; +import io.quarkus.panache.common.deployment.EntityField.EntityFieldAnnotation; +import io.quarkus.panache.common.deployment.EntityModel; +import io.quarkus.panache.common.deployment.MetamodelInfo; +import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; +import io.quarkus.panache.common.deployment.PanacheFieldAccessMethodVisitor; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizer; +import io.quarkus.panache.common.deployment.PanacheMethodCustomizerVisitor; +import io.quarkus.panache.common.deployment.PanacheMovingAnnotationVisitor; + +public abstract class PanacheEntityClassVisitor extends ClassVisitor { + + protected Type thisClass; + protected final Map fields; + private final Set userMethods = new HashSet<>(); + private final MetamodelInfo modelInfo; + protected final ClassInfo panacheEntityBaseClassInfo; + protected ClassInfo entityInfo; + protected List methodCustomizers; + + public PanacheEntityClassVisitor(String className, ClassVisitor outputClassVisitor, + MetamodelInfo> modelInfo, + ClassInfo panacheEntityBaseClassInfo, ClassInfo entityInfo, + List methodCustomizers) { + super(Gizmo.ASM_API_VERSION, outputClassVisitor); + + thisClass = Type.getType("L" + className.replace('.', '/') + ";"); + this.modelInfo = modelInfo; + EntityModel entityModel = modelInfo.getEntityModel(className); + fields = entityModel != null ? entityModel.fields : null; + this.panacheEntityBaseClassInfo = panacheEntityBaseClassInfo; + this.entityInfo = entityInfo; + this.methodCustomizers = methodCustomizers; + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + EntityField ef = fields.get(name); + if (ef == null) { + return super.visitField(access, name, descriptor, signature, value); + } + //we make the fields protected + //so any errors are visible immediately, rather than data just being lost + + FieldVisitor superVisitor; + if (name.equals("id")) { + superVisitor = super.visitField(access, name, descriptor, signature, value); + } else { + superVisitor = super.visitField((access | Modifier.PROTECTED) & ~(Modifier.PRIVATE | Modifier.PUBLIC), + name, descriptor, signature, value); + } + ef.signature = signature; + // if we have a mapped field, let's add some annotations + return new FieldVisitor(Gizmo.ASM_API_VERSION, superVisitor) { + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + if (!descriptor.startsWith(PanacheEntityEnhancer.JAXB_ANNOTATION_PREFIX)) { + return super.visitAnnotation(descriptor, visible); + } else { + // Save off JAX-B annotations on the field so they can be applied to the generated getter later + EntityFieldAnnotation efAnno = new EntityFieldAnnotation(descriptor); + ef.annotations.add(efAnno); + return new PanacheMovingAnnotationVisitor(efAnno); + } + } + + @Override + public void visitEnd() { + // Add the @JaxbTransient property to the field so that JAXB prefers the generated getter (otherwise JAXB complains about + // having a field and property both with the same name) + // JSONB will already use the getter so we're good + // Note: we don't need to check if we already have @XmlTransient in the descriptors because if we did, we moved it to the getter + // so we can't have any duplicate + super.visitAnnotation(PanacheEntityEnhancer.JAXB_TRANSIENT_SIGNATURE, true); + super.visitEnd(); + } + }; + } + + @Override + public MethodVisitor visitMethod(int access, String methodName, String descriptor, String signature, + String[] exceptions) { + userMethods.add(methodName + "/" + descriptor); + MethodVisitor superVisitor = super.visitMethod(access, methodName, descriptor, signature, exceptions); + if (Modifier.isStatic(access) + && Modifier.isPublic(access) + && (access & Opcodes.ACC_SYNTHETIC) == 0 + && !methodCustomizers.isEmpty()) { + org.jboss.jandex.Type[] argTypes = AsmUtil.getParameterTypes(descriptor); + MethodInfo method = this.entityInfo.method(methodName, argTypes); + if (method == null) { + throw new IllegalStateException( + "Could not find indexed method: " + thisClass + "." + methodName + " with descriptor " + descriptor + + " and arg types " + Arrays.toString(argTypes)); + } + superVisitor = new PanacheMethodCustomizerVisitor(superVisitor, method, thisClass, methodCustomizers); + } + return new PanacheFieldAccessMethodVisitor(superVisitor, thisClass.getInternalName(), methodName, descriptor, + modelInfo); + } + + @Override + public void visitEnd() { + // FIXME: generate default constructor + + for (MethodInfo method : panacheEntityBaseClassInfo.methods()) { + // Do not generate a method that already exists + String descriptor = AsmUtil.getDescriptor(method, name -> null); + if (!userMethods.contains(method.name() + "/" + descriptor)) { + AnnotationInstance bridge = method.annotation(PanacheEntityEnhancer.DOTNAME_GENERATE_BRIDGE); + if (bridge != null) { + generateMethod(method, bridge.value("targetReturnTypeErased")); + } + } + } + + generateAccessors(); + + super.visitEnd(); + } + + protected void generateMethod(MethodInfo method, AnnotationValue targetReturnTypeErased) { + String descriptor = AsmUtil.getDescriptor(method, name -> null); + String signature = AsmUtil.getSignature(method, name -> null); + List parameters = method.parameters(); + String castTo = null; + if (targetReturnTypeErased != null && targetReturnTypeErased.asBoolean()) { + castTo = method.returnType().name().toString('/'); + } + + MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, + method.name(), + descriptor, + signature, + null); + for (int i = 0; i < parameters.size(); i++) { + mv.visitParameter(method.parameterName(i), 0 /* modifiers */); + } + mv.visitCode(); + for (PanacheMethodCustomizer customizer : methodCustomizers) { + customizer.customize(thisClass, method, mv); + } + // inject model + injectModel(mv); + for (int i = 0; i < parameters.size(); i++) { + mv.visitIntInsn(Opcodes.ALOAD, i); + } + // inject Class + String forwardingDescriptor = "(" + getModelDescriptor() + descriptor.substring(1); + if (castTo != null) { + // return type is erased to Object + int lastParen = forwardingDescriptor.lastIndexOf(')'); + forwardingDescriptor = forwardingDescriptor.substring(0, lastParen + 1) + "Ljava/lang/Object;"; + } + invokeOperation(method, mv, forwardingDescriptor); + if (castTo != null) + mv.visitTypeInsn(Opcodes.CHECKCAST, castTo); + String returnTypeDescriptor = descriptor.substring(descriptor.lastIndexOf(")") + 1); + mv.visitInsn(AsmUtil.getReturnInstruction(returnTypeDescriptor)); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + protected void invokeOperation(MethodInfo method, MethodVisitor mv, String forwardingDescriptor) { + mv.visitMethodInsn(Opcodes.INVOKESTATIC, + getPanacheOperationsInternalName(), + method.name(), + forwardingDescriptor, false); + } + + protected String getModelDescriptor() { + return "Ljava/lang/Class;"; + } + + protected abstract String getPanacheOperationsInternalName(); + + protected void injectModel(MethodVisitor mv) { + mv.visitLdcInsn(thisClass); + } + + protected void generateAccessors() { + if (fields == null) + return; + for (EntityField field : fields.values()) { + // Getter + String getterName = field.getGetterName(); + String getterDescriptor = "()" + field.descriptor; + if (!userMethods.contains(getterName + "/" + getterDescriptor)) { + MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, + getterName, getterDescriptor, field.signature == null ? null : "()" + field.signature, null); + mv.visitCode(); + mv.visitIntInsn(Opcodes.ALOAD, 0); + generateAccessorGetField(mv, field); + int returnCode = AsmUtil.getReturnInstruction(field.descriptor); + mv.visitInsn(returnCode); + mv.visitMaxs(0, 0); + // Apply JAX-B annotations that are being transferred from the field + for (EntityFieldAnnotation anno : field.annotations) { + anno.writeToVisitor(mv); + } + // Add an explicit Jackson annotation so that the entire property is not ignored due to having @XmlTransient + // on the field + if (shouldAddJsonProperty(field)) { + mv.visitAnnotation(JSON_PROPERTY_SIGNATURE, true); + } + mv.visitEnd(); + } + + // Setter + String setterName = field.getSetterName(); + String setterDescriptor = "(" + field.descriptor + ")V"; + if (!userMethods.contains(setterName + "/" + setterDescriptor)) { + MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, + setterName, setterDescriptor, field.signature == null ? null : "(" + field.signature + ")V", null); + mv.visitCode(); + mv.visitIntInsn(Opcodes.ALOAD, 0); + int loadCode; + switch (field.descriptor) { + case "Z": + case "B": + case "C": + case "S": + case "I": + loadCode = Opcodes.ILOAD; + break; + case "J": + loadCode = Opcodes.LLOAD; + break; + case "F": + loadCode = Opcodes.FLOAD; + break; + case "D": + loadCode = Opcodes.DLOAD; + break; + default: + loadCode = Opcodes.ALOAD; + break; + } + mv.visitIntInsn(loadCode, 1); + generateAccessorSetField(mv, field); + mv.visitInsn(Opcodes.RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + } + } + + private boolean shouldAddJsonProperty(EntityField entityField) { + if (isAnnotatedWithJsonIgnore(entityField)) { + return false; + } + return !entityField.hasAnnotation(JSON_PROPERTY_SIGNATURE); + } + + private boolean isAnnotatedWithJsonIgnore(EntityField entityField) { + FieldInfo field = entityInfo.field(entityField.name); + if (field != null) { + return field.hasAnnotation(JSON_IGNORE_DOT_NAME); + } + + return false; + } + + protected abstract void generateAccessorSetField(MethodVisitor mv, EntityField field); + + protected abstract void generateAccessorGetField(MethodVisitor mv, EntityField field); +} diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/visitors/PanacheRepositoryClassVisitor.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/visitors/PanacheRepositoryClassVisitor.java new file mode 100644 index 0000000000000..44c6a6b70cacb --- /dev/null +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/visitors/PanacheRepositoryClassVisitor.java @@ -0,0 +1,258 @@ +package io.quarkus.panache.common.deployment.visitors; + +import static io.quarkus.deployment.util.AsmUtil.unboxIfRequired; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import io.quarkus.deployment.util.AsmUtil; +import io.quarkus.deployment.util.JandexUtil; +import io.quarkus.gizmo.Gizmo; +import io.quarkus.panache.common.deployment.PanacheEntityEnhancer; + +public abstract class PanacheRepositoryClassVisitor extends ClassVisitor { + + protected Type entityType; + protected String entitySignature; + protected String entityBinaryType; + protected String idSignature; + protected String idBinaryType; + protected String daoBinaryName; + protected ClassInfo daoClassInfo; + protected ClassInfo panacheRepositoryBaseClassInfo; + protected IndexView indexView; + protected Map typeArguments = new HashMap<>(); + // set of name + "/" + descriptor + protected Set userMethods = new HashSet<>(); + + public PanacheRepositoryClassVisitor(String className, ClassVisitor outputClassVisitor, IndexView indexView) { + super(Gizmo.ASM_API_VERSION, outputClassVisitor); + daoClassInfo = indexView.getClassByName(DotName.createSimple(className)); + daoBinaryName = className.replace('.', '/'); + this.indexView = indexView; + } + + protected abstract DotName getPanacheRepositoryDotName(); + + protected abstract DotName getPanacheRepositoryBaseDotName(); + + protected abstract String getPanacheOperationsInternalName(); + + protected String getModelDescriptor() { + return "Ljava/lang/Class;"; + } + + protected void injectModel(MethodVisitor mv) { + // inject Class + mv.visitLdcInsn(entityType); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + + final String repositoryClassName = name.replace('/', '.'); + + String[] foundTypeArguments = findEntityTypeArgumentsForPanacheRepository(indexView, repositoryClassName, + getPanacheRepositoryBaseDotName()); + + entityBinaryType = foundTypeArguments[0]; + entitySignature = "L" + entityBinaryType + ";"; + this.entityType = Type.getType(entitySignature); + idBinaryType = foundTypeArguments[1]; + idSignature = "L" + idBinaryType + ";"; + Type idType = Type.getType(idSignature); + + typeArguments.put("Entity", this.entityType.getDescriptor()); + typeArguments.put("Id", idType.getDescriptor()); + this.panacheRepositoryBaseClassInfo = indexView.getClassByName(getPanacheRepositoryBaseDotName()); + } + + @Override + public MethodVisitor visitMethod(int access, String methodName, String descriptor, String signature, + String[] exceptions) { + userMethods.add(methodName + "/" + descriptor); + return super.visitMethod(access, methodName, descriptor, signature, exceptions); + } + + public static String[] findEntityTypeArgumentsForPanacheRepository(IndexView indexView, + String repositoryClassName, + DotName repositoryDotName) { + for (ClassInfo classInfo : indexView.getAllKnownImplementors(repositoryDotName)) { + if (repositoryClassName.equals(classInfo.name().toString())) { + return recursivelyFindEntityTypeArgumentsFromClass(indexView, classInfo.name(), repositoryDotName); + } + } + + return null; + } + + public static String[] recursivelyFindEntityTypeArgumentsFromClass(IndexView indexView, DotName clazz, + DotName repositoryDotName) { + if (clazz.equals(JandexUtil.DOTNAME_OBJECT)) { + return null; + } + + List typeParameters = JandexUtil + .resolveTypeParameters(clazz, repositoryDotName, indexView); + if (typeParameters.isEmpty()) + throw new IllegalStateException( + "Failed to find supertype " + repositoryDotName + " from entity class " + clazz); + org.jboss.jandex.Type entityType = typeParameters.get(0); + org.jboss.jandex.Type idType = typeParameters.get(1); + return new String[] { + entityType.name().toString().replace('.', '/'), + idType.name().toString().replace('.', '/') + }; + } + + @Override + public void visitEnd() { + for (MethodInfo method : panacheRepositoryBaseClassInfo.methods()) { + // Do not generate a method that already exists + String descriptor = AsmUtil.getDescriptor(method, name -> typeArguments.get(name)); + if (!userMethods.contains(method.name() + "/" + descriptor)) { + AnnotationInstance bridge = method.annotation(PanacheEntityEnhancer.DOTNAME_GENERATE_BRIDGE); + if (bridge != null) { + generateModelBridge(method, bridge.value("targetReturnTypeErased")); + if (needsJvmBridge(method)) { + generateJvmBridge(method); + } + } + } + } + super.visitEnd(); + } + + private boolean needsJvmBridge(MethodInfo method) { + if (needsJvmBridge(method.returnType())) + return true; + for (org.jboss.jandex.Type paramType : method.parameters()) { + if (needsJvmBridge(paramType)) + return true; + } + return false; + } + + private boolean needsJvmBridge(org.jboss.jandex.Type type) { + if (type.kind() == org.jboss.jandex.Type.Kind.TYPE_VARIABLE) { + String typeParamName = type.asTypeVariable().identifier(); + return typeArguments.containsKey(typeParamName); + } + return false; + } + + protected void generateJvmBridge(MethodInfo method) { + // get a bounds-erased descriptor + String descriptor = AsmUtil.getDescriptor(method, name -> null); + // make sure we need a bridge + if (!userMethods.contains(method.name() + "/" + descriptor)) { + MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE, + method.name(), + descriptor, + null, + null); + List parameters = method.parameters(); + for (int i = 0; i < parameters.size(); i++) { + mv.visitParameter(method.parameterName(i), 0 /* modifiers */); + } + mv.visitCode(); + // this + mv.visitIntInsn(Opcodes.ALOAD, 0); + // each param + for (int i = 0; i < parameters.size(); i++) { + org.jboss.jandex.Type paramType = parameters.get(i); + if (paramType.kind() == org.jboss.jandex.Type.Kind.PRIMITIVE) + throw new IllegalStateException("BUG: Don't know how to generate JVM bridge method for " + method + + ": has primitive parameters"); + mv.visitIntInsn(Opcodes.ALOAD, i + 1); + if (paramType.kind() == org.jboss.jandex.Type.Kind.TYPE_VARIABLE) { + String typeParamName = paramType.asTypeVariable().identifier(); + Type type = Type.getType(typeArguments.get(typeParamName)); + if (type.getSort() > Type.DOUBLE) { + mv.visitTypeInsn(Opcodes.CHECKCAST, type.getInternalName()); + } else { + unboxIfRequired(mv, type); + } + } + } + + String targetDescriptor = AsmUtil.getDescriptor(method, name -> typeArguments.get(name)); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + daoBinaryName, + method.name(), + targetDescriptor, false); + String targetReturnTypeDescriptor = targetDescriptor.substring(targetDescriptor.indexOf(')') + 1); + mv.visitInsn(AsmUtil.getReturnInstruction(targetReturnTypeDescriptor)); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } + + } + + protected void generateModelBridge(MethodInfo method, AnnotationValue targetReturnTypeErased) { + String descriptor = AsmUtil.getDescriptor(method, name -> typeArguments.get(name)); + // JpaOperations erases the Id type to Object + String descriptorForJpaOperations = AsmUtil.getDescriptor(method, + name -> name.equals("Entity") ? entitySignature : null); + String signature = AsmUtil.getSignature(method, name -> typeArguments.get(name)); + List parameters = method.parameters(); + + String castTo = null; + if (targetReturnTypeErased != null && targetReturnTypeErased.asBoolean()) { + org.jboss.jandex.Type type = method.returnType(); + if (type.kind() == org.jboss.jandex.Type.Kind.TYPE_VARIABLE && + type.asTypeVariable().identifier().equals("Entity")) { + castTo = entityBinaryType; + } + if (castTo == null) + castTo = type.name().toString('/'); + } + + // Note: we can't use SYNTHETIC here because otherwise Mockito will never mock these methods + MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, + method.name(), + descriptor, + signature, + null); + for (int i = 0; i < parameters.size(); i++) { + mv.visitParameter(method.parameterName(i), 0 /* modifiers */); + } + mv.visitCode(); + injectModel(mv); + for (int i = 0; i < parameters.size(); i++) { + mv.visitIntInsn(Opcodes.ALOAD, i + 1); + } + // inject Class + String forwardingDescriptor = "(" + getModelDescriptor() + descriptorForJpaOperations.substring(1); + if (castTo != null) { + // return type is erased to Object + int lastParen = forwardingDescriptor.lastIndexOf(')'); + forwardingDescriptor = forwardingDescriptor.substring(0, lastParen + 1) + "Ljava/lang/Object;"; + } + mv.visitMethodInsn(Opcodes.INVOKESTATIC, + getPanacheOperationsInternalName(), + method.name(), + forwardingDescriptor, false); + if (castTo != null) + mv.visitTypeInsn(Opcodes.CHECKCAST, castTo); + String returnTypeDescriptor = descriptor.substring(descriptor.lastIndexOf(")") + 1); + mv.visitInsn(AsmUtil.getReturnInstruction(returnTypeDescriptor)); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } +} diff --git a/extensions/panache/pom.xml b/extensions/panache/pom.xml index 39d2a3d946119..11fbc95dc77f0 100644 --- a/extensions/panache/pom.xml +++ b/extensions/panache/pom.xml @@ -19,7 +19,9 @@ hibernate-orm-panache-common hibernate-orm-panache hibernate-orm-panache-kotlin + mongodb-panache-common mongodb-panache + mongodb-panache-kotlin panacheql rest-data-panache hibernate-orm-rest-data-panache diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/JacksonTest.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/JacksonTest.java index 47f341da3d64f..81e59b415072c 100644 --- a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/JacksonTest.java +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/JacksonTest.java @@ -16,6 +16,7 @@ public class JacksonTest { public void testJsonIgnoreHonoured() { List books = RestAssured.when().get("/book/Berlin/Beevor").then().extract().body().jsonPath().getList(".", Book.class); + assertThat(books).hasSize(1).filteredOn(book -> book.author != null).isEmpty(); } } diff --git a/integration-tests/mongodb-panache-kotlin/pom.xml b/integration-tests/mongodb-panache-kotlin/pom.xml new file mode 100755 index 0000000000000..55f9b195a7e64 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/pom.xml @@ -0,0 +1,233 @@ + + + 4.0.0 + + + io.quarkus + quarkus-integration-tests-parent + 999-SNAPSHOT + + + quarkus-integration-test-mongodb-panache-kotlin + + Quarkus - Integration Tests - MongoDB Panache Kotlin + + + + io.quarkus + quarkus-resteasy-jsonb + + + io.quarkus + quarkus-resteasy-mutiny + + + io.quarkus + quarkus-mongodb-panache-kotlin + + + io.quarkus + quarkus-rest-client + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin.version} + + + org.jetbrains.kotlin + kotlin-test + ${kotlin.version} + test + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.jetbrains.kotlin + kotlin-stdlib + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + + + + + + + org.junit.jupiter + junit-jupiter-api + compile + + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-panache-mock + test + + + io.rest-assured + rest-assured + test + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo + test + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + test + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + test + + + org.awaitility + awaitility + test + + + + + src/main/kotlin + src/test/kotlin + + + src/main/resources + true + + + + + org.jetbrains.kotlin + kotlin-maven-plugin + ${kotlin.version} + + + compile + compile + + compile + + + + test-compile + test-compile + + test-compile + + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + + + + all-open + + + + + 1.8 + + + + maven-failsafe-plugin + + true + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + + + native-image + + + native + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + ${native.surefire.skip} + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + false + + + + io.quarkus + quarkus-maven-plugin + + + native-image + + native-image + + + true + true + ${graalvmHome} + + + + + + + + + + diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/Book.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/Book.kt new file mode 100644 index 0000000000000..63d54cd962df3 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/Book.kt @@ -0,0 +1,58 @@ +package io.quarkus.it.mongodb.panache.book + +import io.quarkus.mongodb.panache.MongoEntity +import org.bson.codecs.pojo.annotations.BsonIgnore +import org.bson.codecs.pojo.annotations.BsonProperty +import org.bson.types.ObjectId +import java.time.LocalDate +import javax.json.bind.annotation.JsonbDateFormat + +@MongoEntity(collection = "TheBook", clientName = "cl2") +class Book { + @BsonProperty("bookTitle") + var title: String? = null + private set + var author: String? = null + private set + var id: ObjectId? = null + + @BsonIgnore + var transientDescription: String? = null + private set + + @JsonbDateFormat("yyyy-MM-dd") + var creationDate: LocalDate? = null + var categories = listOf() + private set + private var details: BookDetail? = null + + fun setTitle(title: String?): Book { + this.title = title + return this + } + + fun setAuthor(author: String?): Book { + this.author = author + return this + } + + fun setCategories(categories: List): Book { + this.categories = categories + return this + } + + fun getDetails(): BookDetail? { + return details + } + + fun setDetails(details: BookDetail?): Book { + this.details = details + return this + } + + fun setTransientDescription(transientDescription: String?): Book { + this.transientDescription = transientDescription + return this + } + +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookDetail.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookDetail.kt new file mode 100644 index 0000000000000..5b4d2e2a9a737 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookDetail.kt @@ -0,0 +1,18 @@ +package io.quarkus.it.mongodb.panache.book + +class BookDetail { + var summary: String? = null + private set + var rating = 0 + private set + + fun setSummary(summary: String?): BookDetail { + this.summary = summary + return this + } + + fun setRating(rating: Int): BookDetail { + this.rating = rating + return this + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookEntity.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookEntity.kt new file mode 100644 index 0000000000000..e2fd0b2bd63ec --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookEntity.kt @@ -0,0 +1,59 @@ +package io.quarkus.it.mongodb.panache.book + +import io.quarkus.mongodb.panache.MongoEntity +import io.quarkus.mongodb.panache.kotlin.PanacheMongoCompanion +import io.quarkus.mongodb.panache.kotlin.PanacheMongoEntity +import org.bson.codecs.pojo.annotations.BsonIgnore +import org.bson.codecs.pojo.annotations.BsonProperty +import org.bson.types.ObjectId +import java.time.LocalDate +import javax.json.bind.annotation.JsonbDateFormat + +@MongoEntity(collection = "TheBookEntity", clientName = "cl2") +class BookEntity : PanacheMongoEntity() { + companion object : PanacheMongoCompanion { + override fun findById(id: ObjectId): BookEntity { + return operations.findById(BookEntity::class.java, id) as BookEntity + } + } + + @BsonProperty("bookTitle") + var title: String? = null + private set + var author: String? = null + private set + + @BsonIgnore + var transientDescription: String? = null + + @JsonbDateFormat("yyyy-MM-dd") + var creationDate: LocalDate? = null + var categories = listOf() + private set + private var details: BookDetail? = null + + fun setTitle(title: String?): BookEntity { + this.title = title + return this + } + + fun setAuthor(author: String?): BookEntity { + this.author = author + return this + } + + fun setCategories(categories: List): BookEntity { + this.categories = categories + return this + } + + fun getDetails(): BookDetail? { + return details + } + + fun setDetails(details: BookDetail?): BookEntity { + this.details = details + return this + } + +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookEntityResource.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookEntityResource.kt new file mode 100644 index 0000000000000..914f50c29ee81 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookEntityResource.kt @@ -0,0 +1,111 @@ +package io.quarkus.it.mongodb.panache.book + +import io.quarkus.panache.common.Parameters +import io.quarkus.panache.common.Sort +import org.bson.types.ObjectId +import org.jboss.logging.Logger +import java.net.URI +import java.time.LocalDate +import javax.annotation.PostConstruct +import javax.ws.rs.Consumes +import javax.ws.rs.DELETE +import javax.ws.rs.GET +import javax.ws.rs.NotFoundException +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 + +@Path("/books/entity") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +class BookEntityResource { + @PostConstruct + fun init() { + val databaseName: String = BookEntity.mongoDatabase().name + val collectionName: String = BookEntity.mongoCollection().namespace.collectionName + LOGGER.infov("Using BookEntity[database={0}, collection={1}]", databaseName, collectionName) + } + + @GET + fun getBooks(@QueryParam("sort") sort: String?): List { + return if (sort != null) { + BookEntity.listAll(Sort.ascending(sort)) + } else BookEntity.listAll() + } + + @POST + fun addBook(book: BookEntity): Response { + book.persist() + val id: String = book.id.toString() + return Response.created(URI.create("/books/entity/$id")).build() + } + + @PUT + fun updateBook(book: BookEntity): Response { + book.update() + return Response.accepted().build() + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + fun upsertBook(book: BookEntity): Response { + book.persistOrUpdate() + return Response.accepted().build() + } + + @DELETE + @Path("/{id}") + fun deleteBook(@PathParam("id") id: String?) { + val deleted: Boolean = BookEntity.deleteById(ObjectId(id)) + if (!deleted) { + throw NotFoundException() + } + } + + @GET + @Path("/{id}") + fun getBook(@PathParam("id") id: String?): BookEntity = BookEntity.findById(ObjectId(id)) + + @GET + @Path("/search/{author}") + fun getBooksByAuthor(@PathParam("author") author: String): List = + BookEntity.find("author", author).project(BookShortView::class.java).list() + + @GET + @Path("/search") + fun search(@QueryParam("author") author: String?, @QueryParam("title") title: String?, + @QueryParam("dateFrom") dateFrom: String?, @QueryParam("dateTo") dateTo: String?): BookEntity? { + return if (author != null) { + BookEntity.find("{'author': ?1,'bookTitle': ?2}", author, title!!).firstResult() + } else BookEntity + .find("{'creationDate': {\$gte: ?1}, 'creationDate': {\$lte: ?2}}", LocalDate.parse(dateFrom), + LocalDate.parse(dateTo)) + .firstResult() + } + + @GET + @Path("/search2") + fun search2(@QueryParam("author") author: String?, @QueryParam("title") title: String?, + @QueryParam("dateFrom") dateFrom: String?, @QueryParam("dateTo") dateTo: String?): BookEntity? { + return if (author != null) { + BookEntity.find("{'author': :author,'bookTitle': :title}", + Parameters.with("author", author).and("title", title)).firstResult() + } else BookEntity.find("{'creationDate': {\$gte: :dateFrom}, 'creationDate': {\$lte: :dateTo}}", + Parameters.with("dateFrom", LocalDate.parse(dateFrom)).and("dateTo", LocalDate.parse(dateTo))).firstResult() + } + + @DELETE + fun deleteAll() { + BookEntity.deleteAll() + } + + companion object { + private val LOGGER: Logger = Logger.getLogger(BookEntityResource::class.java) + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookRepository.kt new file mode 100644 index 0000000000000..c2d2bebd4c4e6 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookRepository.kt @@ -0,0 +1,7 @@ +package io.quarkus.it.mongodb.panache.book + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepository +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class BookRepository : PanacheMongoRepository \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookRepositoryResource.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookRepositoryResource.kt new file mode 100644 index 0000000000000..6e0b79389b43b --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookRepositoryResource.kt @@ -0,0 +1,114 @@ +package io.quarkus.it.mongodb.panache.book + +import io.quarkus.panache.common.Parameters +import io.quarkus.panache.common.Sort +import org.bson.types.ObjectId +import org.jboss.logging.Logger +import java.net.URI +import java.time.LocalDate +import javax.annotation.PostConstruct +import javax.inject.Inject +import javax.ws.rs.Consumes +import javax.ws.rs.DELETE +import javax.ws.rs.GET +import javax.ws.rs.NotFoundException +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 + +@Path("/books/repository") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +class BookRepositoryResource { + @Inject + lateinit var bookRepository: BookRepository + + @PostConstruct + fun init() { + val databaseName: String = bookRepository.mongoDatabase().name + val collectionName: String = bookRepository.mongoCollection().namespace.collectionName + LOGGER.infov("Using BookRepository[database={0}, collection={1}]", databaseName, collectionName) + } + + @GET + fun getBooks(@QueryParam("sort") sort: String?): List { + return if (sort != null) { + bookRepository.listAll(Sort.ascending(sort)) + } else bookRepository.listAll() + } + + @POST + fun addBook(book: Book): Response { + bookRepository.persist(book) + return Response.created(URI.create("/books/entity${book.id}")).build() + } + + @PUT + fun updateBook(book: Book): Response { + bookRepository.update(book) + return Response.accepted().build() + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + fun upsertBook(book: Book): Response { + bookRepository.persistOrUpdate(book) + return Response.accepted().build() + } + + @DELETE + @Path("/{id}") + fun deleteBook(@PathParam("id") id: String) { + val deleted: Boolean = bookRepository.deleteById(ObjectId(id)) + if (!deleted) { + throw NotFoundException() + } + } + + @GET + @Path("/{id}") + fun getBook(@PathParam("id") id: String?) = bookRepository.findById(ObjectId(id)) + + @GET + @Path("/search/{author}") + fun getBooksByAuthor(@PathParam("author") author: String): List = + bookRepository.find("author", author).project(BookShortView::class.java).list() + + @GET + @Path("/search") + fun search(@QueryParam("author") author: String?, @QueryParam("title") title: String?, + @QueryParam("dateFrom") dateFrom: String?, @QueryParam("dateTo") dateTo: String?): Book? { + return if (author != null) { + bookRepository.find("{'author': ?1,'bookTitle': ?2}", author, title!!).firstResult() + } else bookRepository + .find("{'creationDate': {\$gte: ?1}, 'creationDate': {\$lte: ?2}}", LocalDate.parse(dateFrom), + LocalDate.parse(dateTo)) + .firstResult() ?: throw NotFoundException() + } + + @GET + @Path("/search2") + fun search2(@QueryParam("author") author: String?, @QueryParam("title") title: String?, + @QueryParam("dateFrom") dateFrom: String?, @QueryParam("dateTo") dateTo: String?): Book? { + return if (author != null) { + bookRepository.find("{'author': :author,'bookTitle': :title}", + Parameters.with("author", author).and("title", title)).firstResult() + } else bookRepository.find("{'creationDate': {\$gte: :dateFrom}, 'creationDate': {\$lte: :dateTo}}", + Parameters.with("dateFrom", LocalDate.parse(dateFrom)).and("dateTo", LocalDate.parse(dateTo))).firstResult() + } + + @DELETE + fun deleteAll() { + bookRepository.deleteAll() + } + + companion object { + private val LOGGER: Logger = Logger.getLogger(BookRepositoryResource::class.java) + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookShortView.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookShortView.kt new file mode 100644 index 0000000000000..fe12bfd04ed45 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/book/BookShortView.kt @@ -0,0 +1,15 @@ +package io.quarkus.it.mongodb.panache.book + +import io.quarkus.mongodb.panache.ProjectionFor +import java.time.LocalDate +import javax.json.bind.annotation.JsonbDateFormat + +@ProjectionFor(Book::class) +class BookShortView { + // uses the field name title and not the column name bookTitle + var title: String? = null + var author: String? = null + + @JsonbDateFormat("yyyy-MM-dd") + var creationDate: LocalDate? = null +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/AbstractRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/AbstractRepository.kt new file mode 100644 index 0000000000000..aa5732ad6656d --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/AbstractRepository.kt @@ -0,0 +1,5 @@ +package io.quarkus.it.mongodb.panache.bugs + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepositoryBase + +abstract class AbstractRepository : PanacheMongoRepositoryBase \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug5274EntityRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug5274EntityRepository.kt new file mode 100644 index 0000000000000..7abdd45fa5479 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug5274EntityRepository.kt @@ -0,0 +1,7 @@ +package io.quarkus.it.mongodb.panache.bugs + +import io.quarkus.it.mongodb.panache.book.Book +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class Bug5274EntityRepository : AbstractRepository() \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug5885AbstractRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug5885AbstractRepository.kt new file mode 100644 index 0000000000000..ec8109aa86bb5 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug5885AbstractRepository.kt @@ -0,0 +1,5 @@ +package io.quarkus.it.mongodb.panache.bugs + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepositoryBase + +abstract class Bug5885AbstractRepository : PanacheMongoRepositoryBase \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug5885EntityRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug5885EntityRepository.kt new file mode 100644 index 0000000000000..fdd2538e763f2 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug5885EntityRepository.kt @@ -0,0 +1,7 @@ +package io.quarkus.it.mongodb.panache.bugs + +import io.quarkus.it.mongodb.panache.person.PersonEntity +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class Bug5885EntityRepository : Bug5885AbstractRepository() \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug6324AbstractRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug6324AbstractRepository.kt new file mode 100644 index 0000000000000..0ad66742a6eee --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug6324AbstractRepository.kt @@ -0,0 +1,5 @@ +package io.quarkus.it.mongodb.panache.bugs + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepository + +abstract class Bug6324AbstractRepository : PanacheMongoRepository \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug6324ConcreteRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug6324ConcreteRepository.kt new file mode 100644 index 0000000000000..4b98163786cb5 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug6324ConcreteRepository.kt @@ -0,0 +1,6 @@ +package io.quarkus.it.mongodb.panache.bugs + +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class Bug6324ConcreteRepository : Bug6324AbstractRepository() \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug6324Repository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug6324Repository.kt new file mode 100644 index 0000000000000..78ed184bd4736 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/Bug6324Repository.kt @@ -0,0 +1,7 @@ +package io.quarkus.it.mongodb.panache.bugs + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepository +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class Bug6324Repository : PanacheMongoRepository \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/BugResource.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/BugResource.kt new file mode 100644 index 0000000000000..06b7af4bb4463 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/BugResource.kt @@ -0,0 +1,94 @@ +package io.quarkus.it.mongodb.panache.bugs + +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.temporal.ChronoUnit +import java.util.Date +import javax.inject.Inject +import javax.ws.rs.Consumes +import javax.ws.rs.GET +import javax.ws.rs.NotFoundException +import javax.ws.rs.Path +import javax.ws.rs.Produces +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response + +@Path("/bugs") +@Produces(MediaType.TEXT_PLAIN) +@Consumes(MediaType.TEXT_PLAIN) +class BugResource { + @Inject + lateinit var bug5274EntityRepository: Bug5274EntityRepository + + @Inject + lateinit var bug5885EntityRepository: Bug5885EntityRepository + + @Inject + lateinit var bug6324Repository: Bug6324Repository + + @Inject + lateinit var bug6324ConcreteRepository: Bug6324ConcreteRepository + + @GET + @Path("5274") + fun testBug5274(): String { + bug5274EntityRepository.count() + return "OK" + } + + @GET + @Path("5885") + fun testBug5885(): String { + bug5885EntityRepository.findById(1L) + return "OK" + } + + @GET + @Path("6324") + fun testNeedReflection(): Response { + return Response.ok(bug6324Repository.listAll()).build() + } + + @GET + @Path("6324/abstract") + fun testNeedReflectionAndAbstract(): Response { + return Response.ok(bug6324ConcreteRepository.listAll()).build() + } + + @GET + @Path("dates") + fun testDatesFormat(): Response { + val dateEntity = DateEntity() + dateEntity.persist() + + // search on all possible fields + val millisInDay = 1000 * 60 * 60 * 24.toLong() + val dateTomorrow = Date(System.currentTimeMillis() + 1000 * millisInDay) + val localDateTomorrow: LocalDate = LocalDate.now().plus(1, ChronoUnit.DAYS) + val localDateTimeTomorrow: LocalDateTime = LocalDateTime.now().plus(1, ChronoUnit.DAYS) + val instantTomorrow: Instant = Instant.now().plus(1, ChronoUnit.DAYS) + val result: DateEntity = DateEntity + .find("dateDate < ?1 and localDate < ?2 and localDateTime < ?3 and instant < ?4", + dateTomorrow, localDateTomorrow, localDateTimeTomorrow, instantTomorrow) + .firstResult() + ?: return Response.status(404).build() + return Response.ok().build() + } + + @GET + @Path("7415") + fun testForeignObjectId(): Response { + val link = LinkedEntity() + link.name = "toto" + link.persist() + val entity = LinkedEntity() + entity.name = "tata" + entity.myForeignId = link.id + entity.persist() + + // we should be able to retrieve `entity` from the foreignId ... + LinkedEntity.find("myForeignId", link.id!!).firstResult() ?: throw NotFoundException() + return Response.ok().build() + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/DateEntity.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/DateEntity.kt new file mode 100644 index 0000000000000..b9390d4ab5f6d --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/DateEntity.kt @@ -0,0 +1,19 @@ +package io.quarkus.it.mongodb.panache.bugs + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoCompanion +import io.quarkus.mongodb.panache.kotlin.PanacheMongoEntity +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.util.Date + +/** + * An entity that have all the supported date format. + * Asserting #6566 and possibility other date issues. + */ +class DateEntity(var dateDate: Date = Date(), var localDate: LocalDate = LocalDate.now(), + var localDateTime: LocalDateTime = LocalDateTime.now(), + var instant: Instant = Instant.now()) : PanacheMongoEntity() { + companion object: PanacheMongoCompanion + +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/LinkedEntity.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/LinkedEntity.kt new file mode 100644 index 0000000000000..72fecd3e9a8d4 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/LinkedEntity.kt @@ -0,0 +1,12 @@ +package io.quarkus.it.mongodb.panache.bugs + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoCompanion +import io.quarkus.mongodb.panache.kotlin.PanacheMongoEntity +import org.bson.types.ObjectId + +class LinkedEntity : PanacheMongoEntity() { + companion object: PanacheMongoCompanion + + var name: String? = null + var myForeignId: ObjectId? = null +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/NeedReflection.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/NeedReflection.kt new file mode 100644 index 0000000000000..5af253182e2c2 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/bugs/NeedReflection.kt @@ -0,0 +1,5 @@ +package io.quarkus.it.mongodb.panache.bugs + +class NeedReflection { + var comment: String? = null +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/MockablePersonRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/MockablePersonRepository.kt new file mode 100644 index 0000000000000..3da2463cbbb89 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/MockablePersonRepository.kt @@ -0,0 +1,14 @@ +package io.quarkus.it.mongodb.panache.person + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepositoryBase +import io.quarkus.panache.common.Sort +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class MockablePersonRepository : PanacheMongoRepositoryBase { + open fun findOrdered(): List? { + val sort = Sort.by("lastname", "firstname") + val found = findAll(sort) + return found.list() + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/Person.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/Person.kt new file mode 100644 index 0000000000000..fd83f6ebecf8c --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/Person.kt @@ -0,0 +1,10 @@ +package io.quarkus.it.mongodb.panache.person + +import org.bson.codecs.pojo.annotations.BsonId + +class Person { + @BsonId + var id: Long? = null + var firstname: String? = null + var lastname: String? = null +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonEntity.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonEntity.kt new file mode 100644 index 0000000000000..790cc1d2fff1c --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonEntity.kt @@ -0,0 +1,20 @@ +package io.quarkus.it.mongodb.panache.person + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoCompanionBase +import io.quarkus.mongodb.panache.kotlin.PanacheMongoEntityBase +import io.quarkus.panache.common.Sort +import org.bson.codecs.pojo.annotations.BsonId + +class PersonEntity : PanacheMongoEntityBase() { + @BsonId + var id: Long? = null + var firstname: String? = null + var lastname: String? = null + + companion object : PanacheMongoCompanionBase { + fun findOrdered(): List { + return findAll(Sort.by("lastname", "firstname")).list() + } + + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonEntityResource.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonEntityResource.kt new file mode 100644 index 0000000000000..89f7a2d2a9caa --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonEntityResource.kt @@ -0,0 +1,87 @@ +package io.quarkus.it.mongodb.panache.person + +import io.quarkus.panache.common.Sort +import java.net.URI +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 + +@Path("/persons/entity") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +class PersonEntityResource { + @GET + fun getPersons(@QueryParam("sort") sort: String?): List = + sort?.let { PersonEntity.listAll(Sort.ascending(sort)) } + ?: PersonEntity.listAll() + + @GET + @Path("/search/{name}") + fun searchPersons(@PathParam("name") name: String): Set { + return PersonEntity.find("lastname", name) + .project(PersonName::class.java) + .list() + .toSet() + } + + @POST + fun addPerson(person: PersonEntity): Response { + person.persist() + return Response.created(URI.create("/persons/entity/${person.id}")).build() + } + + @POST + @Path("/multiple") + fun addPersons(persons: List) { + PersonEntity.persist(persons) + } + + @PUT + fun updatePerson(person: PersonEntity): Response { + person.update() + return Response.accepted().build() + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + fun upsertPerson(person: PersonEntity): Response { + person.persistOrUpdate() + return Response.accepted().build() + } + + @DELETE + @Path("/{id}") + fun deletePerson(@PathParam("id") id: String) { + PersonEntity.deleteById(id.toLong()) + } + + @GET + @Path("/{id}") + fun getPerson(@PathParam("id") id: String) = PersonEntity.findById(id.toLong()) + + @GET + @Path("/count") + fun countAll(): Long = PersonEntity.count() + + @DELETE + fun deleteAll() { + PersonEntity.deleteAll() + } + + @POST + @Path("/rename") + fun rename(@QueryParam("previousName") previousName: String, @QueryParam("newName") newName: String): Response { + PersonEntity.update("lastname", newName) + .where("lastname", previousName) + return Response.ok().build() + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonName.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonName.kt new file mode 100644 index 0000000000000..5808cd7e3df1b --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonName.kt @@ -0,0 +1,18 @@ +package io.quarkus.it.mongodb.panache.person + +class PersonName { + var lastname: String? = null + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PersonName) return false + + if (lastname != other.lastname) return false + + return true + } + + override fun hashCode(): Int { + return lastname?.hashCode() ?: 0 + } + +} diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonRepository.kt new file mode 100644 index 0000000000000..d38f6b57708ab --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonRepository.kt @@ -0,0 +1,8 @@ +package io.quarkus.it.mongodb.panache.person + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepositoryBase +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class PersonRepository : PanacheMongoRepositoryBase { +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonRepositoryResource.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonRepositoryResource.kt new file mode 100644 index 0000000000000..21f4581f533d8 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/person/PersonRepositoryResource.kt @@ -0,0 +1,98 @@ +package io.quarkus.it.mongodb.panache.person + +import io.quarkus.panache.common.Sort +import java.net.URI +import javax.inject.Inject +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 + +@Path("/persons/repository") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +class PersonRepositoryResource { + // fake unused injection point to force ArC to not remove this otherwise I can't mock it in the tests + @Inject + lateinit var mockablePersonRepository: MockablePersonRepository + + @Inject + lateinit var personRepository: PersonRepository + + @GET + fun getPersons(@QueryParam("sort") sort: String?): List { + return sort?.let { + personRepository.listAll(Sort.ascending(sort)) + } ?: personRepository.listAll() + } + + @GET + @Path("/search/{name}") + fun searchPersons(@PathParam("name") name: String): Set { + return personRepository.find("lastname", name) + .project(PersonName::class.java) + .list() + .toSet() + } + + @POST + fun addPerson(person: Person): Response { + personRepository.persist(person) + return Response.created(URI.create("/persons/repository/${person.id}")).build() + } + + @POST + @Path("/multiple") + fun addPersons(persons: List) { + personRepository.persist(persons) + } + + @PUT + fun updatePerson(person: Person): Response { + personRepository.update(person) + return Response.accepted().build() + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + fun upsertPerson(person: Person): Response { + personRepository.persistOrUpdate(person) + return Response.accepted().build() + } + + @DELETE + @Path("/{id}") + fun deletePerson(@PathParam("id") id: String) { + val person = personRepository.findById(id.toLong()) + personRepository.delete(person!!) + } + + + @GET + @Path("/{id}") + fun getPerson(@PathParam("id") id: String) = personRepository.findById(id.toLong()) + + @GET + @Path("/count") + fun countAll(): Long = personRepository.count() + + @DELETE + fun deleteAll() { + personRepository.deleteAll() + } + + @POST + @Path("/rename") + fun rename(@QueryParam("previousName") previousName: String, @QueryParam("newName") newName: String): Response { + personRepository.update("lastname", newName).where("lastname", previousName) + return Response.ok().build() + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntity.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntity.kt new file mode 100644 index 0000000000000..38ebce70fb33b --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntity.kt @@ -0,0 +1,53 @@ +package io.quarkus.it.mongodb.panache.reactive.book + +import io.quarkus.it.mongodb.panache.book.BookDetail +import io.quarkus.mongodb.panache.MongoEntity +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoCompanion +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoEntity +import org.bson.codecs.pojo.annotations.BsonIgnore +import org.bson.codecs.pojo.annotations.BsonProperty +import java.time.LocalDate +import java.util.ArrayList +import javax.json.bind.annotation.JsonbDateFormat + +@MongoEntity(collection = "TheBookEntity", clientName = "cl2") +class ReactiveBookEntity : ReactivePanacheMongoEntity() { + companion object: ReactivePanacheMongoCompanion + + @BsonProperty("bookTitle") + var title: String? = null + private set + var author: String? = null + private set + + @BsonIgnore + var transientDescription: String? = null + + @JsonbDateFormat("yyyy-MM-dd") + var creationDate: LocalDate? = null + var categories: List = ArrayList() + private set + var details: BookDetail? = null + private set + + fun setTitle(title: String?): ReactiveBookEntity { + this.title = title + return this + } + + fun setAuthor(author: String?): ReactiveBookEntity { + this.author = author + return this + } + + fun setCategories(categories: List): ReactiveBookEntity { + this.categories = categories + return this + } + + fun setDetails(details: BookDetail?): ReactiveBookEntity { + this.details = details + return this + } + +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntityResource.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntityResource.kt new file mode 100644 index 0000000000000..2ff7361b04f8a --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntityResource.kt @@ -0,0 +1,125 @@ +package io.quarkus.it.mongodb.panache.reactive.book + +import io.quarkus.panache.common.Parameters.with +import io.quarkus.panache.common.Sort +import io.smallrye.mutiny.Uni +import org.bson.types.ObjectId +import org.jboss.logging.Logger +import org.jboss.resteasy.annotations.SseElementType +import org.reactivestreams.Publisher +import java.net.URI +import java.time.LocalDate.parse +import javax.annotation.PostConstruct +import javax.ws.rs.Consumes +import javax.ws.rs.DELETE +import javax.ws.rs.GET +import javax.ws.rs.NotFoundException +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 + +@Path("/reactive/books/entity") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +class ReactiveBookEntityResource { + @PostConstruct + fun init() { + val databaseName: String = ReactiveBookEntity.mongoDatabase().name + val collectionName: String = ReactiveBookEntity.mongoCollection().namespace.collectionName + LOGGER.infov("Using BookEntity[database={0}, collection={1}]", databaseName, collectionName) + } + + @GET + fun getBooks(@QueryParam("sort") sort: String?): Uni> { + return if (sort != null) { + ReactiveBookEntity.listAll(Sort.ascending(sort)) + } else ReactiveBookEntity.listAll() + } + + @GET + @Path("/stream") + @Produces(MediaType.SERVER_SENT_EVENTS) + @SseElementType(MediaType.APPLICATION_JSON) + fun streamBooks(@QueryParam("sort") sort: String?): Publisher { + return if (sort != null) { + ReactiveBookEntity.streamAll(Sort.ascending(sort)) + } else ReactiveBookEntity.streamAll() + } + + @POST + fun addBook(book: ReactiveBookEntity): Uni { + return book.persist().map { + //the ID is populated before sending it to the database + Response.created(URI.create("/books/entity${book.id}")).build() + } + } + + @PUT + fun updateBook(book: ReactiveBookEntity): Uni = book.update().map { _ -> Response.accepted().build() } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + fun upsertBook(book: ReactiveBookEntity): Uni = + book.persistOrUpdate().map { v -> Response.accepted().build() } + + @DELETE + @Path("/{id}") + fun deleteBook(@PathParam("id") id: String?): Uni { + return ReactiveBookEntity.deleteById(ObjectId(id)) + .map { d -> + if (d) { + return@map null + } + throw NotFoundException() + } + } + + @GET + @Path("/{id}") + fun getBook(@PathParam("id") id: String?): Uni = ReactiveBookEntity.findById(ObjectId(id)) + + @GET + @Path("/search/{author}") + fun getBooksByAuthor(@PathParam("author") author: String): Uni> = + ReactiveBookEntity.list("author", author) + + @GET + @Path("/search") + fun search(@QueryParam("author") author: String?, @QueryParam("title") title: String?, + @QueryParam("dateFrom") dateFrom: String?, @QueryParam("dateTo") dateTo: String?): Uni { + return if (author != null) { + ReactiveBookEntity.find("{'author': ?1,'bookTitle': ?2}", author, title!!).firstResult() + } else ReactiveBookEntity + .find("{'creationDate': {\$gte: ?1}, 'creationDate': {\$lte: ?2}}", parse(dateFrom), + parse(dateTo)) + .firstResult() + } + + @GET + @Path("/search2") + fun search2(@QueryParam("author") author: String?, @QueryParam("title") title: String?, + @QueryParam("dateFrom") dateFrom: String?, @QueryParam("dateTo") dateTo: String?) + : Uni = + + if (author != null) { + ReactiveBookEntity.find("{'author': :author,'bookTitle': :title}", + with("author", author).and("title", title)).firstResult() + } else { + ReactiveBookEntity.find("{'creationDate': {\$gte: :dateFrom}, 'creationDate': {\$lte: :dateTo}}", + with("dateFrom", parse(dateFrom)).and("dateTo", parse(dateTo))) + .firstResult() + } + + @DELETE + fun deleteAll(): Uni = ReactiveBookEntity.deleteAll().map { l -> null } + + companion object { + private val LOGGER: Logger = Logger.getLogger(ReactiveBookEntityResource::class.java) + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepository.kt new file mode 100644 index 0000000000000..cae81fd8d66ce --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepository.kt @@ -0,0 +1,8 @@ +package io.quarkus.it.mongodb.panache.reactive.book + +import io.quarkus.it.mongodb.panache.book.Book +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoRepository +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class ReactiveBookRepository : ReactivePanacheMongoRepository \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepositoryResource.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepositoryResource.kt new file mode 100644 index 0000000000000..55a94730ce1ad --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepositoryResource.kt @@ -0,0 +1,128 @@ +package io.quarkus.it.mongodb.panache.reactive.book + +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 +import org.bson.types.ObjectId +import org.jboss.logging.Logger +import org.jboss.resteasy.annotations.SseElementType +import org.reactivestreams.Publisher +import java.net.URI +import java.time.LocalDate +import javax.annotation.PostConstruct +import javax.inject.Inject +import javax.ws.rs.Consumes +import javax.ws.rs.DELETE +import javax.ws.rs.GET +import javax.ws.rs.NotFoundException +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 + +@Path("/reactive/books/repository") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +class ReactiveBookRepositoryResource { + @Inject + lateinit var reactiveBookRepository: ReactiveBookRepository + + @PostConstruct + fun init() { + val databaseName: String = reactiveBookRepository.mongoDatabase().name + val collectionName: String = reactiveBookRepository.mongoCollection().namespace.collectionName + LOGGER.infov("Using BookRepository[database={0}, collection={1}]", databaseName, collectionName) + } + + @GET + fun getBooks(@QueryParam("sort") sort: String?): Uni> { + return if (sort != null) { + reactiveBookRepository.listAll(Sort.ascending(sort)) + } else reactiveBookRepository.listAll() + } + + @GET + @Path("/stream") + @Produces(MediaType.SERVER_SENT_EVENTS) + @SseElementType(MediaType.APPLICATION_JSON) + fun streamBooks(@QueryParam("sort") sort: String?): Publisher { + return if (sort != null) { + reactiveBookRepository.streamAll(Sort.ascending(sort)) + } else reactiveBookRepository.streamAll() + } + + @POST + fun addBook(book: Book): Uni { + return reactiveBookRepository.persist(book).map { + //the ID is populated before sending it to the database + Response.created(URI.create("/books/entity${book.id}")).build() + } + } + + @PUT + fun updateBook(book: Book): Uni = reactiveBookRepository.update(book).map { Response.accepted().build() } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + fun upsertBook(book: Book): Uni = + reactiveBookRepository.persistOrUpdate(book).map { Response.accepted().build() } + + @DELETE + @Path("/{id}") + fun deleteBook(@PathParam("id") id: String?): Uni { + return reactiveBookRepository.deleteById(ObjectId(id)) + .map { d -> + if (d) { + return@map null + } + throw NotFoundException() + } + } + + @GET + @Path("/{id}") + fun getBook(@PathParam("id") id: String?): Uni = reactiveBookRepository.findById(ObjectId(id)) + + @GET + @Path("/search/{author}") + fun getBooksByAuthor(@PathParam("author") author: String): Uni> = + reactiveBookRepository.list("author", author) + + @GET + @Path("/search") + fun search(@QueryParam("author") author: String?, @QueryParam("title") title: String?, + @QueryParam("dateFrom") dateFrom: String?, @QueryParam("dateTo") dateTo: String?): Uni { + return if (author != null) { + reactiveBookRepository.find("{'author': ?1,'bookTitle': ?2}", author, title!!).firstResult() + } else { + reactiveBookRepository + .find("{'creationDate': {\$gte: ?1}, 'creationDate': {\$lte: ?2}}", LocalDate.parse(dateFrom), + LocalDate.parse(dateTo)) + .firstResult() + } + } + + @GET + @Path("/search2") + fun search2(@QueryParam("author") author: String?, @QueryParam("title") title: String?, + @QueryParam("dateFrom") dateFrom: String?, @QueryParam("dateTo") dateTo: String?): Uni { + return if (author != null) { + reactiveBookRepository.find("{'author': :author,'bookTitle': :title}", + Parameters.with("author", author).and("title", title)).firstResult() + } else reactiveBookRepository.find("{'creationDate': {\$gte: :dateFrom}, 'creationDate': {\$lte: :dateTo}}", + Parameters.with("dateFrom", LocalDate.parse(dateFrom)).and("dateTo", LocalDate.parse(dateTo))).firstResult() + } + + @DELETE + fun deleteAll(): Uni = reactiveBookRepository.deleteAll().map { null } + + companion object { + private val LOGGER: Logger = Logger.getLogger(ReactiveBookRepositoryResource::class.java) + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntity.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntity.kt new file mode 100644 index 0000000000000..2e7e76a27d276 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntity.kt @@ -0,0 +1,14 @@ +package io.quarkus.it.mongodb.panache.reactive.person + +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoCompanionBase +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoEntityBase +import org.bson.codecs.pojo.annotations.BsonId + +class ReactivePersonEntity : ReactivePanacheMongoEntityBase() { + companion object: ReactivePanacheMongoCompanionBase + + @BsonId + var id: Long? = null + var firstname: String? = null + var lastname: String? = null +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntityResource.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntityResource.kt new file mode 100644 index 0000000000000..346e4f22e383a --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntityResource.kt @@ -0,0 +1,88 @@ +package io.quarkus.it.mongodb.panache.reactive.person + + +import io.quarkus.it.mongodb.panache.person.PersonName +import io.quarkus.panache.common.Sort +import io.smallrye.mutiny.Uni +import java.net.URI +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 + +@Path("/reactive/persons/entity") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +class ReactivePersonEntityResource { + @GET + fun getPersons(@QueryParam("sort") sort: String?): Uni> { + return if (sort != null) { + ReactivePersonEntity.listAll(Sort.ascending(sort)) + } else ReactivePersonEntity.listAll() + } + + @GET + @Path("/search/{name}") + fun searchPersons(@PathParam("name") name: String): Set { + val uniqueNames = mutableSetOf() + val lastnames: List = ReactivePersonEntity.find("lastname", name) + .project(PersonName::class.java) + .list() + .await() + .indefinitely() + lastnames.forEach { p -> uniqueNames.add(p) } + return uniqueNames + } + + @POST + fun addPerson(person: ReactivePersonEntity): Uni { + return person.persist() + .map { Response.created(URI.create("/persons/entity${person.id}")).build() } + } + + @POST + @Path("/multiple") + fun addPersons(persons: List): Uni = ReactivePersonEntity.persist(persons) + + @PUT + fun updatePerson(person: ReactivePersonEntity): Uni = + person.update().map { Response.accepted().build() } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + fun upsertPerson(person: ReactivePersonEntity): Uni = + person.persistOrUpdate().map { Response.accepted().build() } + + @DELETE + @Path("/{id}") + fun deletePerson(@PathParam("id") id: String): Uni = + ReactivePersonEntity.findById(id.toLong()).flatMap { person -> person.delete() } + + @GET + @Path("/{id}") + fun getPerson(@PathParam("id") id: String): Uni = + ReactivePersonEntity.findById(id.toLong()) + + @GET + @Path("/count") + fun countAll(): Uni = ReactivePersonEntity.count() + + @DELETE + fun deleteAll(): Uni = ReactivePersonEntity.deleteAll().map { null } + + @POST + @Path("/rename") + fun rename(@QueryParam("previousName") previousName: String, @QueryParam("newName") newName: String): Uni { + return ReactivePersonEntity.update("lastname", newName) + .where("lastname", previousName) + .map { count -> Response.ok().build() } + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepository.kt new file mode 100644 index 0000000000000..e14dd7e9e5379 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepository.kt @@ -0,0 +1,8 @@ +package io.quarkus.it.mongodb.panache.reactive.person + +import io.quarkus.it.mongodb.panache.person.Person +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoRepositoryBase +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class ReactivePersonRepository : ReactivePanacheMongoRepositoryBase \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepositoryResource.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepositoryResource.kt new file mode 100644 index 0000000000000..2dccf07605b22 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepositoryResource.kt @@ -0,0 +1,95 @@ +package io.quarkus.it.mongodb.panache.reactive.person + + +import io.quarkus.it.mongodb.panache.person.Person +import io.quarkus.it.mongodb.panache.person.PersonName +import io.quarkus.panache.common.Sort +import io.smallrye.mutiny.Uni +import java.net.URI +import javax.inject.Inject +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 + +@Path("/reactive/persons/repository") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +class ReactivePersonRepositoryResource { + @Inject + lateinit var reactivePersonRepository: ReactivePersonRepository + + @GET + fun getPersons(@QueryParam("sort") sort: String?): Uni> { + return if (sort != null) { + reactivePersonRepository.listAll(Sort.ascending(sort)) + } else reactivePersonRepository.listAll() + } + + @GET + @Path("/search/{name}") + fun searchPersons(@PathParam("name") name: String): Set { + val uniqueNames = mutableSetOf() + val lastnames: List = reactivePersonRepository.find("lastname", name) + .project(PersonName::class.java) + .list() + .await() + .indefinitely() + lastnames.forEach { p -> uniqueNames.add(p) } // this will throw if it's not the right type + return uniqueNames + } + + @POST + fun addPerson(person: Person): Uni { + return reactivePersonRepository.persist(person).map { + //the ID is populated before sending it to the database + Response.created(URI.create("/persons/entity${person.id}")).build() + } + } + + @POST + @Path("/multiple") + fun addPersons(persons: List): Uni = reactivePersonRepository.persist(persons) + + @PUT + fun updatePerson(person: Person): Uni = + reactivePersonRepository.update(person).map { Response.accepted().build() } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + fun upsertPerson(person: Person): Uni = + reactivePersonRepository.persistOrUpdate(person).map { v -> Response.accepted().build() } + + @DELETE + @Path("/{id}") + fun deletePerson(@PathParam("id") id: String): Uni { + return reactivePersonRepository.findById(id.toLong()) + .flatMap { person -> reactivePersonRepository.delete(person) } + } + + @GET + @Path("/{id}") + fun getPerson(@PathParam("id") id: String): Uni = reactivePersonRepository.findById(id.toLong()) + + @GET + @Path("/count") + fun countAll(): Uni = reactivePersonRepository.count() + + @DELETE + fun deleteAll(): Uni = reactivePersonRepository.deleteAll().map { null } + + @POST + @Path("/rename") + fun rename(@QueryParam("previousName") previousName: String, @QueryParam("newName") newName: String): Uni { + return reactivePersonRepository.update("lastname", newName).where("lastname", previousName) + .map { count -> Response.ok().build() } + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestImperativeEntity.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestImperativeEntity.kt new file mode 100644 index 0000000000000..952e43611e65f --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestImperativeEntity.kt @@ -0,0 +1,19 @@ +package io.quarkus.it.mongodb.panache.test + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoCompanion +import io.quarkus.mongodb.panache.kotlin.PanacheMongoEntity + +class TestImperativeEntity : PanacheMongoEntity { + companion object : PanacheMongoCompanion + + lateinit var title: String + lateinit var category: String + lateinit var description: String + + constructor() {} + constructor(title: String, category: String, description: String) { + this.title = title + this.category = category + this.description = description + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestImperativeRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestImperativeRepository.kt new file mode 100644 index 0000000000000..19fb094764583 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestImperativeRepository.kt @@ -0,0 +1,7 @@ +package io.quarkus.it.mongodb.panache.test + +import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepository +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class TestImperativeRepository : PanacheMongoRepository \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestReactiveEntity.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestReactiveEntity.kt new file mode 100644 index 0000000000000..f1df4203bbd3d --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestReactiveEntity.kt @@ -0,0 +1,18 @@ +package io.quarkus.it.mongodb.panache.test + +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoCompanion +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoEntity + +class TestReactiveEntity : ReactivePanacheMongoEntity { + companion object : ReactivePanacheMongoCompanion + lateinit var title: String + lateinit var category: String + lateinit var description: String + + constructor() {} + constructor(title: String, category: String, description: String) { + this.title = title + this.category = category + this.description = description + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestReactiveRepository.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestReactiveRepository.kt new file mode 100644 index 0000000000000..383a4855c3420 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestReactiveRepository.kt @@ -0,0 +1,7 @@ +package io.quarkus.it.mongodb.panache.test + +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheMongoRepository +import javax.enterprise.context.ApplicationScoped + +@ApplicationScoped +class TestReactiveRepository : ReactivePanacheMongoRepository \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestResource.kt b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestResource.kt new file mode 100644 index 0000000000000..9fff5f13c6a6d --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/kotlin/io/quarkus/it/mongodb/panache/test/TestResource.kt @@ -0,0 +1,745 @@ +package io.quarkus.it.mongodb.panache.test + +import com.mongodb.client.model.Collation +import com.mongodb.client.model.CollationStrength +import io.quarkus.mongodb.panache.kotlin.PanacheQuery +import io.quarkus.mongodb.panache.kotlin.reactive.ReactivePanacheQuery +import io.quarkus.panache.common.Page +import io.quarkus.panache.common.Parameters +import io.quarkus.panache.common.Sort +import org.bson.Document +import org.junit.jupiter.api.Assertions +import javax.inject.Inject +import javax.ws.rs.GET +import javax.ws.rs.Path +import javax.ws.rs.core.Response + +@Path("/test") +class TestResource { + @Inject + lateinit var testImperativeRepository: TestImperativeRepository + + @Inject + lateinit var testReactiveRepository: TestReactiveRepository + + private val testImperativeEntities: List by lazy { + (0..9).map { TestImperativeEntity("title$it", "category" + it % 2, "description$it") } + } + + @GET + @Path("imperative/entity") + fun testImperativeEntity(): Response { + val entities: List = testImperativeEntities + + // insert all + Assertions.assertEquals(0, TestImperativeEntity.count()) + TestImperativeEntity.persist(entities) + Assertions.assertEquals(10, TestImperativeEntity.count()) + + // varargs + val entity11 = TestImperativeEntity("title11", "category", "desc") + val entity12 = TestImperativeEntity("title11", "category", "desc") + TestImperativeEntity.persist(entity11, entity12) + Assertions.assertEquals(12, TestImperativeEntity.count()) + entity11.category = "categoryUpdated" + entity12.category = "categoryUpdated" + TestImperativeEntity.update(entity11, entity12) + entity11.delete() + entity12.delete() + TestImperativeEntity.persistOrUpdate(entity11, entity12) + Assertions.assertEquals(12, TestImperativeEntity.count()) + entity11.category = "categoryUpdated" + entity12.category = "categoryUpdated" + TestImperativeEntity.persistOrUpdate(entity11, entity12) + entity11.delete() + entity12.delete() + Assertions.assertEquals(10, TestImperativeEntity.count()) + + // paginate + testImperativePagination(TestImperativeEntity.findAll()) + + // range + testImperativeRange(TestImperativeEntity.findAll()) + + // query + Assertions.assertEquals(5, TestImperativeEntity.list("category", "category0").size) + Assertions.assertEquals(5, TestImperativeEntity.list("category = ?1", "category0").size) + Assertions.assertEquals(5, TestImperativeEntity.list("category = :category", + Parameters.with("category", "category1")).size) + Assertions.assertEquals(5, TestImperativeEntity.list("{'category' : ?1}", "category0").size) + Assertions.assertEquals(5, TestImperativeEntity.list("{'category' : :category}", + Parameters.with("category", "category1")).size) + val listQuery: Document = Document().append("category", "category1") + Assertions.assertEquals(5, TestImperativeEntity.list(listQuery).size) + Assertions.assertEquals(0, TestImperativeEntity.list("category", null).size) + Assertions.assertEquals(0, TestImperativeEntity.list("category = :category", + Parameters.with("category", null)).size) + + // regex + val entityWithUpperCase = TestImperativeEntity("title11", "upperCaseCategory", "desc") + entityWithUpperCase.persist() + Assertions.assertEquals(1, TestImperativeEntity.list("category like ?1", "upperCase.*").size) + Assertions.assertEquals(1, TestImperativeEntity.list("category like ?1", "/uppercase.*/i").size) + entityWithUpperCase.delete() + + // sort + val entityA = TestImperativeEntity("aaa", "aaa", "aaa") + entityA.persist() + val entityZ = TestImperativeEntity("zzz", "zzz", "zzz") + entityZ.persistOrUpdate() + var result: TestImperativeEntity = TestImperativeEntity.listAll(Sort.ascending("title"))[0] + Assertions.assertEquals("aaa", result.title) + result = TestImperativeEntity.listAll(Sort.descending("title"))[0] + Assertions.assertEquals("zzz", result.title) + entityA.delete() + entityZ.delete() + + // collation + val entityALower = TestImperativeEntity("aaa", "aaa", "aaa") + entityALower.persist() + val entityAUpper = TestImperativeEntity("AAA", "AAA", "AAA") + entityAUpper.persist() + val entityB = TestImperativeEntity("BBB", "BBB", "BBB") + entityB.persistOrUpdate() + var results: List = TestImperativeEntity.listAll(Sort.ascending("title")) + Assertions.assertEquals("AAA", results[0].title) + Assertions.assertEquals("BBB", results[1].title) + Assertions.assertEquals("aaa", results[2].title) + val collation: Collation = Collation.builder().caseLevel(true).collationStrength(CollationStrength.PRIMARY).locale("fr") + .build() + results = TestImperativeEntity.findAll(Sort.ascending("title")).withCollation(collation).list() + Assertions.assertEquals("aaa", results[0].title) + Assertions.assertEquals("AAA", results[1].title) + Assertions.assertEquals("BBB", results[2].title) + entityAUpper.delete() + entityALower.delete() + entityB.delete() + + // count + Assertions.assertEquals(5, TestImperativeEntity.count("category", "category0")) + Assertions.assertEquals(5, TestImperativeEntity.count("category = ?1", "category0")) + Assertions.assertEquals(5, TestImperativeEntity.count("category = :category", + Parameters.with("category", "category1"))) + Assertions.assertEquals(5, TestImperativeEntity.count("{'category' : ?1}", "category0")) + Assertions.assertEquals(5, TestImperativeEntity.count("{'category' : :category}", + Parameters.with("category", "category1"))) + val countQuery: Document = Document().append("category", "category1") + Assertions.assertEquals(5, TestImperativeEntity.count(countQuery)) + + // update + val list: List = TestImperativeEntity.list("category = ?1", "category0") + Assertions.assertEquals(5, list.size) + for (entity in list) { + entity.category = "newCategory" + } + TestImperativeEntity.update(list) + TestImperativeEntity.update(list.stream()) + TestImperativeEntity.persistOrUpdate(list) + var updated: Long = TestImperativeEntity.update("category", "newCategory2").where("category", "newCategory") + Assertions.assertEquals(5, updated) + updated = TestImperativeEntity.update("category = ?1", "newCategory").where("category = ?1", "newCategory2") + Assertions.assertEquals(5, updated) + updated = TestImperativeEntity.update("{'category' : ?1}", "newCategory2").where("{'category' : ?1}", "newCategory") + Assertions.assertEquals(5, updated) + updated = TestImperativeEntity.update("category = :category", Parameters.with("category", "newCategory")) + .where("category = :category", Parameters.with("category", "newCategory2")) + Assertions.assertEquals(5, updated) + updated = TestImperativeEntity.update("{'category' : :category}", Parameters.with("category", "newCategory2")) + .where("{'category' : :category}", Parameters.with("category", "newCategory")) + Assertions.assertEquals(5, updated) + Assertions.assertEquals(5, TestImperativeEntity.count("category = ?1", "newCategory2")) + updated = TestImperativeEntity.update("newField", "newValue").all() + Assertions.assertEquals(10, updated) + + // delete + TestImperativeEntity.delete("category = ?1", "newCategory2") + TestImperativeEntity.delete("{'category' : ?1}", "category1") + Assertions.assertEquals(0, TestImperativeEntity.count()) + TestImperativeEntity.persist(entities.stream()) + TestImperativeEntity.delete("category = :category", Parameters.with("category", "category0")) + TestImperativeEntity.delete("{'category' : :category}", Parameters.with("category", "category1")) + Assertions.assertEquals(0, TestImperativeEntity.count()) + TestImperativeEntity.persistOrUpdate(entities.stream()) + TestImperativeEntity.delete("category", "category0") + TestImperativeEntity.delete("category", "category1") + Assertions.assertEquals(0, TestImperativeEntity.count()) + return Response.ok().build() + } + + @GET + @Path("imperative/repository") + fun testImperativeRepository(): Response { + val entities: List = testImperativeEntities + + // insert all + Assertions.assertEquals(0, testImperativeRepository.count()) + testImperativeRepository.persist(entities) + Assertions.assertEquals(10, testImperativeRepository.count()) + + // varargs + val entity11 = TestImperativeEntity("title11", "category", "desc") + val entity12 = TestImperativeEntity("title11", "category", "desc") + testImperativeRepository.persist(entity11, entity12) + Assertions.assertEquals(12, testImperativeRepository.count()) + entity11.category = "categoryUpdated" + entity12.category = "categoryUpdated" + testImperativeRepository.update(entity11, entity12) + entity11.delete() + entity12.delete() + testImperativeRepository.persistOrUpdate(entity11, entity12) + Assertions.assertEquals(12, testImperativeRepository.count()) + entity11.category = "categoryUpdated" + entity12.category = "categoryUpdated" + testImperativeRepository.persistOrUpdate(entity11, entity12) + entity11.delete() + entity12.delete() + Assertions.assertEquals(10, testImperativeRepository.count()) + + // paginate + testImperativePagination(testImperativeRepository.findAll()) + + // range + testImperativeRange(testImperativeRepository.findAll()) + + // query + Assertions.assertEquals(5, testImperativeRepository.list("category", "category0").size) + Assertions.assertEquals(5, testImperativeRepository.list("category = ?1", "category0").size) + Assertions.assertEquals(5, testImperativeRepository.list("category = :category", + Parameters.with("category", "category1")).size) + Assertions.assertEquals(5, testImperativeRepository.list("{'category' : ?1}", "category0").size) + Assertions.assertEquals(5, testImperativeRepository.list("{'category' : :category}", + Parameters.with("category", "category1")).size) + val listQuery: Document = Document().append("category", "category1") + Assertions.assertEquals(5, testImperativeRepository.list(listQuery).size) + Assertions.assertEquals(0, testImperativeRepository.list("category", null).size) + Assertions.assertEquals(0, testImperativeRepository.list("category = :category", + Parameters.with("category", null)).size) + + // regex + val entityWithUpperCase = TestImperativeEntity("title11", "upperCaseCate)gory", "desc") + testImperativeRepository.persist(entityWithUpperCase) + Assertions.assertEquals(1, testImperativeRepository.list("category like ?1", "upperCase.*").size) + Assertions.assertEquals(1, testImperativeRepository.list("category like ?1", "/uppercase.*/i").size) + testImperativeRepository.delete(entityWithUpperCase) + + // sort + val entityA = TestImperativeEntity("aaa", "aaa", "aaa") + testImperativeRepository.persist(entityA) + val entityZ = TestImperativeEntity("zzz", "zzz", "zzz") + testImperativeRepository.persistOrUpdate(entityZ) + var result: TestImperativeEntity = testImperativeRepository.listAll(Sort.ascending("title"))[0] + Assertions.assertEquals("aaa", result.title) + result = testImperativeRepository.listAll(Sort.descending("title"))[0] + Assertions.assertEquals("zzz", result.title) + testImperativeRepository.delete(entityA) + testImperativeRepository.delete(entityZ) + + // collation + val entityALower = TestImperativeEntity("aaa", "aaa", "aaa") + testImperativeRepository.persist(entityALower) + val entityAUpper = TestImperativeEntity("AAA", "AAA", "AAA") + testImperativeRepository.persist(entityAUpper) + val entityB = TestImperativeEntity("BBB", "BBB", "BBB") + testImperativeRepository.persistOrUpdate(entityB) + var results: List = testImperativeRepository.listAll(Sort.ascending("title")) + Assertions.assertEquals("AAA", results[0].title) + Assertions.assertEquals("BBB", results[1].title) + Assertions.assertEquals("aaa", results[2].title) + val collation: Collation = Collation.builder().caseLevel(true).collationStrength(CollationStrength.PRIMARY).locale("fr") + .build() + results = testImperativeRepository.findAll(Sort.ascending("title")) + .withCollation(collation) + .list() + Assertions.assertEquals("aaa", results[0].title) + Assertions.assertEquals("AAA", results[1].title) + Assertions.assertEquals("BBB", results[2].title) + testImperativeRepository.delete(entityALower) + testImperativeRepository.delete(entityAUpper) + testImperativeRepository.delete(entityB) + + // count + Assertions.assertEquals(5, testImperativeRepository.count("category", "category0")) + Assertions.assertEquals(5, testImperativeRepository.count("category = ?1", "category0")) + Assertions.assertEquals(5, testImperativeRepository.count("category = :category", + Parameters.with("category", "category1"))) + Assertions.assertEquals(5, testImperativeRepository.count("{'category' : ?1}", "category0")) + Assertions.assertEquals(5, testImperativeRepository.count("{'category' : :category}", + Parameters.with("category", "category1"))) + val countQuery: Document = Document().append("category", "category1") + Assertions.assertEquals(5, testImperativeRepository.count(countQuery)) + + // update + val list: List = testImperativeRepository.list("category = ?1", "category0") + Assertions.assertEquals(5, list.size) + for (entity in list) { + entity.category = "newCategory" + } + testImperativeRepository.update(list) + testImperativeRepository.update(list.stream()) + testImperativeRepository.persistOrUpdate(list) + var updated: Long = testImperativeRepository.update("category", "newCategory2").where("category", "newCategory") + Assertions.assertEquals(5, updated) + updated = testImperativeRepository.update("category = ?1", "newCategory").where("category = ?1", "newCategory2") + Assertions.assertEquals(5, updated) + updated = testImperativeRepository.update("{'category' : ?1}", "newCategory2").where("{'category' : ?1}", + "newCategory") + Assertions.assertEquals(5, updated) + updated = testImperativeRepository.update("category = :category", Parameters.with("category", "newCategory")) + .where("category = :category", Parameters.with("category", "newCategory2")) + Assertions.assertEquals(5, updated) + updated = testImperativeRepository.update("{'category' : :category}", Parameters.with("category", "newCategory2")) + .where("{'category' : :category}", Parameters.with("category", "newCategory")) + Assertions.assertEquals(5, updated) + Assertions.assertEquals(5, testImperativeRepository.count("category = ?1", "newCategory2")) + updated = testImperativeRepository.update("newField", "newValue").all() + Assertions.assertEquals(10, updated) + + // delete + testImperativeRepository.delete("category = ?1", "newCategory2") + testImperativeRepository.delete("{'category' : ?1}", "category1") + Assertions.assertEquals(0, testImperativeRepository.count()) + testImperativeRepository.persist(entities.stream()) + testImperativeRepository.delete("category = :category", Parameters.with("category", "category0")) + testImperativeRepository.delete("{'category' : :category}", Parameters.with("category", "category1")) + Assertions.assertEquals(0, testImperativeRepository.count()) + testImperativeRepository.persistOrUpdate(entities.stream()) + testImperativeRepository.delete("category", "category0") + testImperativeRepository.delete("category", "category1") + Assertions.assertEquals(0, testImperativeRepository.count()) + return Response.ok().build() + } + + private fun testImperativePagination(query: PanacheQuery) { + query.page(0, 4) + Assertions.assertEquals(3, query.pageCount()) + var page: List = query.list() + Assertions.assertEquals(4, page.size) + Assertions.assertTrue(query.hasNextPage()) + Assertions.assertFalse(query.hasPreviousPage()) + query.nextPage() + page = query.list() + Assertions.assertEquals(4, page.size) + Assertions.assertTrue(query.hasNextPage()) + Assertions.assertTrue(query.hasPreviousPage()) + query.lastPage() + page = query.list() + Assertions.assertEquals(2, page.size) + Assertions.assertFalse(query.hasNextPage()) + Assertions.assertTrue(query.hasPreviousPage()) + query.firstPage() + page = query.list() + Assertions.assertEquals(4, page.size) + Assertions.assertTrue(query.hasNextPage()) + Assertions.assertFalse(query.hasPreviousPage()) + query.page(Page.of(1, 5)) + Assertions.assertEquals(2, query.pageCount()) + page = query.list() + Assertions.assertEquals(5, page.size) + Assertions.assertFalse(query.hasNextPage()) + Assertions.assertTrue(query.hasPreviousPage()) + + // mix page with range + page = query.page(0, 3).range(0, 1).list() + Assertions.assertEquals(2, page.size) + } + + private fun testImperativeRange(query: PanacheQuery) { + query.range(0, 3) + var range: List = query.list() + Assertions.assertEquals(4, range.size) + range = query.range(4, 7).list() + Assertions.assertEquals(4, range.size) + range = query.range(8, 12).list() + Assertions.assertEquals(2, range.size) + range = query.range(10, 12).list() + Assertions.assertEquals(0, range.size) + + // when using range, we cannot use any of the page related operations + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).nextPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).previousPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).pageCount() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).lastPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).firstPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).hasPreviousPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).hasNextPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).page() } + + // but this is valid to switch from range to page + range = query.range(0, 2).page(0, 3).list() + Assertions.assertEquals(3, range.size) + } + + @GET + @Path("reactive/entity") + fun testReactiveEntity(): Response { + val entities: List = testReactiveEntities + + // insert all + Assertions.assertEquals(0, TestReactiveEntity.count().await().indefinitely()) + TestReactiveEntity.persist(entities).await().indefinitely() + Assertions.assertEquals(10, TestReactiveEntity.count().await().indefinitely()) + + // varargs + val entity11 = TestReactiveEntity("title11", "category", "desc") + val entity12 = TestReactiveEntity("title11", "category", "desc") + TestReactiveEntity.persist(entity11, entity12).await().indefinitely() + Assertions.assertEquals(12, TestReactiveEntity.count().await().indefinitely()) + entity11.category = "categoryUpdated" + entity12.category = "categoryUpdated" + TestReactiveEntity.update(entity11, entity12).await().indefinitely() + entity11.delete().await().indefinitely() + entity12.delete().await().indefinitely() + TestReactiveEntity.persistOrUpdate(entity11, entity12).await().indefinitely() + Assertions.assertEquals(12, TestReactiveEntity.count().await().indefinitely()) + entity11.category = "categoryUpdated" + entity12.category = "categoryUpdated" + TestReactiveEntity.persistOrUpdate(entity11, entity12).await().indefinitely() + entity11.delete().await().indefinitely() + entity12.delete().await().indefinitely() + Assertions.assertEquals(10, TestReactiveEntity.count().await().indefinitely()) + + // paginate + testReactivePagination(TestReactiveEntity.findAll()) + + // range + testReactiveRange(TestReactiveEntity.findAll()) + + // query + Assertions.assertEquals(5, + TestReactiveEntity.list("category", "category0").await().indefinitely().size) + Assertions.assertEquals(5, + TestReactiveEntity.list("category = ?1", "category0").await().indefinitely().size) + Assertions.assertEquals(5, TestReactiveEntity.list("category = :category", + Parameters.with("category", "category1")).await().indefinitely().size) + Assertions.assertEquals(5, + TestReactiveEntity.list("{'category' : ?1}", "category0").await().indefinitely().size) + Assertions.assertEquals(5, TestReactiveEntity.list("{'category' : :category}", + Parameters.with("category", "category1")).await().indefinitely().size) + val listQuery: Document = Document().append("category", "category1") + Assertions.assertEquals(5, TestReactiveEntity.list(listQuery).await().indefinitely().size) + Assertions.assertEquals(0, TestReactiveEntity.list("category", null).await().indefinitely().size) + Assertions.assertEquals(0, TestReactiveEntity.list("category = :category", + Parameters.with("category", null)).await().indefinitely().size) + + // regex + val entityWithUpperCase = TestReactiveEntity("title11", "upperCaseCategory", "desc") + entityWithUpperCase.persist().await().indefinitely() + Assertions.assertEquals(1, TestReactiveEntity.list("category like ?1", "upperCase.*") + .await().indefinitely().size) + Assertions.assertEquals(1, TestReactiveEntity.list("category like ?1", "/uppercase.*/i") + .await().indefinitely().size) + entityWithUpperCase.delete().await().indefinitely() + + // sort + val entityA = TestReactiveEntity("aaa", "aaa", "aaa") + entityA.persist().await().indefinitely() + val entityZ = TestReactiveEntity("zzz", "zzz", "zzz") + entityZ.persistOrUpdate().await().indefinitely() + var result: TestReactiveEntity = TestReactiveEntity.listAll(Sort.ascending("title")).await() + .indefinitely()[0] + Assertions.assertEquals("aaa", result.title) + result = TestReactiveEntity.listAll(Sort.descending("title")).await().indefinitely()[0] + Assertions.assertEquals("zzz", result.title) + entityA.delete().await().indefinitely() + entityZ.delete().await().indefinitely() + + // collation + val entityALower = TestReactiveEntity("aaa", "aaa", "aaa") + entityALower.persist().await().indefinitely() + val entityAUpper = TestReactiveEntity("AAA", "AAA", "AAA") + entityAUpper.persist().await().indefinitely() + val entityB = TestReactiveEntity("BBB", "BBB", "BBB") + entityB.persistOrUpdate().await().indefinitely() + var results: List = TestReactiveEntity.listAll(Sort.ascending("title")).await() + .indefinitely() + Assertions.assertEquals("AAA", results[0].title) + Assertions.assertEquals("BBB", results[1].title) + Assertions.assertEquals("aaa", results[2].title) + val collation: Collation = Collation.builder().caseLevel(true).collationStrength(CollationStrength.PRIMARY).locale("fr") + .build() + results = TestReactiveEntity.findAll( Sort.ascending("title")).withCollation(collation).list() + .await().indefinitely() + Assertions.assertEquals("aaa", results[0].title) + Assertions.assertEquals("AAA", results[1].title) + Assertions.assertEquals("BBB", results[2].title) + entityAUpper.delete().await().indefinitely() + entityALower.delete().await().indefinitely() + entityB.delete().await().indefinitely() + + // count + Assertions.assertEquals(5, TestReactiveEntity.count("category", "category0").await().indefinitely()) + Assertions.assertEquals(5, TestReactiveEntity.count("category = ?1", "category0").await().indefinitely()) + Assertions.assertEquals(5, TestReactiveEntity.count("category = :category", + Parameters.with("category", "category1")).await().indefinitely()) + Assertions.assertEquals(5, TestReactiveEntity.count("{'category' : ?1}", "category0").await().indefinitely()) + Assertions.assertEquals(5, TestReactiveEntity.count("{'category' : :category}", + Parameters.with("category", "category1")).await().indefinitely()) + val countQuery: Document = Document().append("category", "category1") + Assertions.assertEquals(5, TestReactiveEntity.count(countQuery).await().indefinitely()) + + // update + val list: List = TestReactiveEntity.list("category = ?1", "category0").await() + .indefinitely() + Assertions.assertEquals(5, list.size) + for (entity in list) { + entity.category = "newCategory" + } + TestReactiveEntity.update(list).await().indefinitely() + TestReactiveEntity.update(list.stream()).await().indefinitely() + TestReactiveEntity.persistOrUpdate(list).await().indefinitely() + var updated: Long = TestReactiveEntity.update("category", "newCategory2").where("category", "newCategory").await() + .indefinitely() + Assertions.assertEquals(5, updated) + updated = TestReactiveEntity.update("category = ?1", "newCategory").where("category = ?1", "newCategory2").await() + .indefinitely() + Assertions.assertEquals(5, updated) + updated = TestReactiveEntity.update("{'category' : ?1}", "newCategory2").where("{'category' : ?1}", "newCategory") + .await().indefinitely() + Assertions.assertEquals(5, updated) + updated = TestReactiveEntity.update("category = :category", Parameters.with("category", "newCategory")) + .where("category = :category", Parameters.with("category", "newCategory2")).await().indefinitely() + Assertions.assertEquals(5, updated) + updated = TestReactiveEntity.update("{'category' : :category}", Parameters.with("category", "newCategory2")) + .where("{'category' : :category}", Parameters.with("category", "newCategory")).await().indefinitely() + Assertions.assertEquals(5, updated) + Assertions.assertEquals(5, TestReactiveEntity.count("category = ?1", "newCategory2").await().indefinitely()) + updated = TestReactiveEntity.update("newField", "newValue").all().await().indefinitely() + Assertions.assertEquals(10, updated) + + // delete + TestReactiveEntity.delete("category = ?1", "newCategory2").await().indefinitely() + TestReactiveEntity.delete("{'category' : ?1}", "category1").await().indefinitely() + Assertions.assertEquals(0, TestReactiveEntity.count().await().indefinitely()) + TestReactiveEntity.persist(entities.stream()).await().indefinitely() + TestReactiveEntity.delete("category = :category", Parameters.with("category", "category0")).await().indefinitely() + TestReactiveEntity.delete("{'category' : :category}", Parameters.with("category", "category1")).await().indefinitely() + Assertions.assertEquals(0, TestReactiveEntity.count().await().indefinitely()) + TestReactiveEntity.persistOrUpdate(entities.stream()).await().indefinitely() + TestReactiveEntity.delete("category", "category0").await().indefinitely() + TestReactiveEntity.delete("category", "category1").await().indefinitely() + Assertions.assertEquals(0, TestReactiveEntity.count().await().indefinitely()) + return Response.ok().build() + } + + @GET + @Path("reactive/repository") + fun testReactiveRepository(): Response { + val entities: List = testReactiveEntities + + // insert all + Assertions.assertEquals(0, testReactiveRepository.count().await().indefinitely()) + testReactiveRepository.persist(entities).await().indefinitely() + Assertions.assertEquals(10, testReactiveRepository.count().await().indefinitely()) + + // varargs + val entity11 = TestReactiveEntity("title11", "category", "desc") + val entity12 = TestReactiveEntity("title11", "category", "desc") + testReactiveRepository.persist(entity11, entity12).await().indefinitely() + Assertions.assertEquals(12, testReactiveRepository.count().await().indefinitely()) + entity11.category = "categoryUpdated" + entity12.category = "categoryUpdated" + testReactiveRepository.update(entity11, entity12).await().indefinitely() + entity11.delete().await().indefinitely() + entity12.delete().await().indefinitely() + testReactiveRepository.persistOrUpdate(entity11, entity12).await().indefinitely() + Assertions.assertEquals(12, testReactiveRepository.count().await().indefinitely()) + entity11.category = "categoryUpdated" + entity12.category = "categoryUpdated" + testReactiveRepository.persistOrUpdate(entity11, entity12).await().indefinitely() + entity11.delete().await().indefinitely() + entity12.delete().await().indefinitely() + Assertions.assertEquals(10, testReactiveRepository.count().await().indefinitely()) + + // paginate + testReactivePagination(testReactiveRepository.findAll()) + + // range + testReactiveRange(testReactiveRepository.findAll()) + + // query + Assertions.assertEquals(5, + testReactiveRepository.list("category", "category0").await().indefinitely().size) + Assertions.assertEquals(5, + testReactiveRepository.list("category = ?1", "category0").await().indefinitely().size) + Assertions.assertEquals(5, testReactiveRepository.list("category = :category", + Parameters.with("category", "category1")).await().indefinitely().size) + Assertions.assertEquals(5, + testReactiveRepository.list("{'category' : ?1}", "category0").await().indefinitely().size) + Assertions.assertEquals(5, testReactiveRepository.list("{'category' : :category}", + Parameters.with("category", "category1")).await().indefinitely().size) + val listQuery: Document = Document().append("category", "category1") + Assertions.assertEquals(5, testReactiveRepository.list(listQuery).await().indefinitely().size) + Assertions.assertEquals(0, testReactiveRepository.list("category", null).await().indefinitely().size) + Assertions.assertEquals(0, testReactiveRepository.list("category = :category", + Parameters.with("category", null)).await().indefinitely().size) + + // regex + val entityWithUpperCase = TestReactiveEntity("title11", "upperCaseCategory", "desc") + testReactiveRepository.persist(entityWithUpperCase).await().indefinitely() + Assertions.assertEquals(1, testReactiveRepository.list("category like ?1", "upperCase.*") + .await().indefinitely().size) + Assertions.assertEquals(1, testReactiveRepository.list("category like ?1", "/uppercase.*/i") + .await().indefinitely().size) + testReactiveRepository.delete(entityWithUpperCase).await().indefinitely() + + // sort + val entityA = TestReactiveEntity("aaa", "aaa", "aaa") + testReactiveRepository.persist(entityA).await().indefinitely() + val entityZ = TestReactiveEntity("zzz", "zzz", "zzz") + testReactiveRepository.persistOrUpdate(entityZ).await().indefinitely() + var result: TestReactiveEntity = testReactiveRepository.listAll(Sort.ascending("title")).await().indefinitely()[0] + Assertions.assertEquals("aaa", result.title) + result = testReactiveRepository.listAll(Sort.descending("title")).await().indefinitely()[0] + Assertions.assertEquals("zzz", result.title) + testReactiveRepository.delete(entityA).await().indefinitely() + testReactiveRepository.delete(entityZ).await().indefinitely() + + // collation + val entityALower = TestReactiveEntity("aaa", "aaa", "aaa") + testReactiveRepository.persist(entityALower).await().indefinitely() + val entityAUpper = TestReactiveEntity("AAA", "AAA", "AAA") + testReactiveRepository.persist(entityAUpper).await().indefinitely() + val entityB = TestReactiveEntity("BBB", "BBB", "BBB") + testReactiveRepository.persistOrUpdate(entityB).await().indefinitely() + var results: List = testReactiveRepository.listAll(Sort.ascending("title")).await().indefinitely() + Assertions.assertEquals("AAA", results[0].title) + Assertions.assertEquals("BBB", results[1].title) + Assertions.assertEquals("aaa", results[2].title) + val collation: Collation = Collation.builder().caseLevel(true).collationStrength(CollationStrength.PRIMARY).locale("fr") + .build() + results = testReactiveRepository.findAll(Sort.ascending("title")).withCollation(collation).list().await() + .indefinitely() + Assertions.assertEquals("aaa", results[0].title) + Assertions.assertEquals("AAA", results[1].title) + Assertions.assertEquals("BBB", results[2].title) + testReactiveRepository.delete(entityALower).await().indefinitely() + testReactiveRepository.delete(entityAUpper).await().indefinitely() + testReactiveRepository.delete(entityB).await().indefinitely() + + // count + Assertions.assertEquals(5, + testReactiveRepository.count("category", "category0").await().indefinitely()) + Assertions.assertEquals(5, + testReactiveRepository.count("category = ?1", "category0").await().indefinitely()) + Assertions.assertEquals(5, testReactiveRepository.count("category = :category", + Parameters.with("category", "category1")).await().indefinitely()) + Assertions.assertEquals(5, + testReactiveRepository.count("{'category' : ?1}", "category0").await().indefinitely()) + Assertions.assertEquals(5, testReactiveRepository.count("{'category' : :category}", + Parameters.with("category", "category1")).await().indefinitely()) + val countQuery: Document = Document().append("category", "category1") + Assertions.assertEquals(5, testReactiveRepository.count(countQuery).await().indefinitely()) + + // update + val list: List = testReactiveRepository.list("category = ?1", "category0").await().indefinitely() + Assertions.assertEquals(5, list.size) + for (entity in list) { + entity.category = "newCategory" + } + testReactiveRepository.update(list).await().indefinitely() + testReactiveRepository.update(list.stream()).await().indefinitely() + testReactiveRepository.persistOrUpdate(list).await().indefinitely() + var updated: Long = testReactiveRepository.update("category", "newCategory2").where("category", "newCategory").await() + .indefinitely() + Assertions.assertEquals(5, updated) + updated = testReactiveRepository.update("category = ?1", "newCategory").where("category = ?1", "newCategory2").await() + .indefinitely() + Assertions.assertEquals(5, updated) + updated = testReactiveRepository.update("{'category' : ?1}", "newCategory2").where("{'category' : ?1}", "newCategory") + .await().indefinitely() + Assertions.assertEquals(5, updated) + updated = testReactiveRepository.update("category = :category", Parameters.with("category", "newCategory")) + .where("category = :category", Parameters.with("category", "newCategory2")).await().indefinitely() + Assertions.assertEquals(5, updated) + updated = testReactiveRepository.update("{'category' : :category}", Parameters.with("category", "newCategory2")) + .where("{'category' : :category}", Parameters.with("category", "newCategory")).await().indefinitely() + Assertions.assertEquals(5, updated) + Assertions.assertEquals(5, testReactiveRepository.count("category = ?1", "newCategory2").await().indefinitely()) + updated = testReactiveRepository.update("newField", "newValue").all().await().indefinitely() + Assertions.assertEquals(10, updated) + + // delete + testReactiveRepository.delete("category = ?1", "newCategory2").await().indefinitely() + testReactiveRepository.delete("{'category' : ?1}", "category1").await().indefinitely() + Assertions.assertEquals(0, testReactiveRepository.count().await().indefinitely()) + testReactiveRepository.persist(entities.stream()).await().indefinitely() + testReactiveRepository.delete("category = :category", Parameters.with("category", "category0")).await().indefinitely() + testReactiveRepository.delete("{'category' : :category}", Parameters.with("category", "category1")).await() + .indefinitely() + Assertions.assertEquals(0, testReactiveRepository.count().await().indefinitely()) + testReactiveRepository.persistOrUpdate(entities.stream()).await().indefinitely() + testReactiveRepository.delete("category", "category0").await().indefinitely() + testReactiveRepository.delete("category", "category1").await().indefinitely() + Assertions.assertEquals(0, testReactiveRepository.count().await().indefinitely()) + return Response.ok().build() + } + + private val testReactiveEntities: List + get() { + return (0..9).map { + TestReactiveEntity("title$it", + "category" + it % 2, + "description$it") + } + } + + private fun testReactivePagination(query: ReactivePanacheQuery) { + query.page(0, 4) + Assertions.assertEquals(3, query.pageCount().await().indefinitely()) + var page: List = query.list().await().indefinitely() + Assertions.assertEquals(4, page.size) + Assertions.assertTrue(query.hasNextPage().await().indefinitely()) + Assertions.assertFalse(query.hasPreviousPage()) + query.nextPage() + page = query.list().await().indefinitely() + Assertions.assertEquals(4, page.size) + Assertions.assertTrue(query.hasNextPage().await().indefinitely()) + Assertions.assertTrue(query.hasPreviousPage()) + query.lastPage().await().indefinitely() + page = query.list().await().indefinitely() + Assertions.assertEquals(2, page.size) + Assertions.assertFalse(query.hasNextPage().await().indefinitely()) + Assertions.assertTrue(query.hasPreviousPage()) + query.firstPage() + page = query.list().await().indefinitely() + Assertions.assertEquals(4, page.size) + Assertions.assertTrue(query.hasNextPage().await().indefinitely()) + Assertions.assertFalse(query.hasPreviousPage()) + query.page(Page.of(1, 5)) + Assertions.assertEquals(2, query.pageCount().await().indefinitely()) + page = query.list().await().indefinitely() + Assertions.assertEquals(5, page.size) + Assertions.assertFalse(query.hasNextPage().await().indefinitely()) + Assertions.assertTrue(query.hasPreviousPage()) + + // mix page with range + page = query.page(0, 3).range(0, 1).list().await().indefinitely() + Assertions.assertEquals(2, page.size) + } + + private fun testReactiveRange(query: ReactivePanacheQuery) { + query.range(0, 3) + var range: List = query.list().await().indefinitely() + Assertions.assertEquals(4, range.size) + range = query.range(4, 7).list().await().indefinitely() + Assertions.assertEquals(4, range.size) + range = query.range(8, 12).list().await().indefinitely() + Assertions.assertEquals(2, range.size) + range = query.range(10, 12).list().await().indefinitely() + Assertions.assertEquals(0, range.size) + + // when using range, we cannot use any of the page related operations + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).nextPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).previousPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).pageCount() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).lastPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).firstPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).hasPreviousPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).hasNextPage() } + Assertions.assertThrows(UnsupportedOperationException::class.java) { query.range(0, 2).page() } + + // but this is valid to switch from range to page + range = query.range(0, 2).page(0, 3).list().await().indefinitely() + Assertions.assertEquals(3, range.size) + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/main/resources/application.properties b/integration-tests/mongodb-panache-kotlin/src/main/resources/application.properties new file mode 100644 index 0000000000000..b8c6ae9b25bfb --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/main/resources/application.properties @@ -0,0 +1,9 @@ +quarkus.mongodb.connection-string=mongodb://localhost:27018 +quarkus.mongodb.write-concern.journal=false +quarkus.mongodb.database=books + +# fake a different MongoDB instance +quarkus.mongodb.cl2.connection-string=mongodb://localhost:27018 +quarkus.mongodb.cl2.write-concern.journal=false + +quarkus.log.category."io.quarkus.mongodb.panache.runtime".level=DEBUG diff --git a/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/BookDTO.kt b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/BookDTO.kt new file mode 100644 index 0000000000000..37149010476f5 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/BookDTO.kt @@ -0,0 +1,73 @@ +package io.quarkus.it.mongodb.panache + +import com.fasterxml.jackson.annotation.JsonFormat +import io.quarkus.it.mongodb.panache.book.BookDetail +import java.util.ArrayList +import java.util.Date + +/** + * The IT uses a DTO and not directly the Book object because it should avoid the usage of ObjectId. + */ +class BookDTO { + var title: String? = null + private set + var author: String? = null + private set + var id: String? = null + var transientDescription: String? = null + private set + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") + private var creationDate: Date? = null + var categories: List = ArrayList() + private set + var details: BookDetail? = null + private set + + fun setTitle(title: String?): BookDTO { + this.title = title + return this + } + + fun setAuthor(author: String?): BookDTO { + this.author = author + return this + } + + fun setCategories(categories: List): BookDTO { + this.categories = categories + return this + } + + fun setDetails(details: BookDetail?): BookDTO { + this.details = details + return this + } + + fun setTransientDescription(transientDescription: String?): BookDTO { + this.transientDescription = transientDescription + return this + } + + fun getCreationDate(): Date? { + return creationDate + } + + fun setCreationDate(creationDate: Date?): BookDTO { + this.creationDate = creationDate + return this + } + + @Override + override fun toString(): String { + return "BookDTO{" + + "title='" + title + '\'' + + ", author='" + author + '\'' + + ", id='" + id + '\'' + + ", transientDescription='" + transientDescription + '\'' + + ", creationDate='" + creationDate + '\'' + + ", categories=" + categories + + ", details=" + details + + '}' + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/MongoTestResource.kt b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/MongoTestResource.kt new file mode 100644 index 0000000000000..4edf887275b18 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/MongoTestResource.kt @@ -0,0 +1,66 @@ +package io.quarkus.it.mongodb.panache + +import de.flapdoodle.embed.mongo.MongodExecutable +import de.flapdoodle.embed.mongo.MongodStarter +import de.flapdoodle.embed.mongo.config.IMongodConfig +import de.flapdoodle.embed.mongo.config.MongodConfigBuilder +import de.flapdoodle.embed.mongo.config.Net +import de.flapdoodle.embed.mongo.distribution.Version +import de.flapdoodle.embed.process.runtime.Network +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager +import org.jboss.logging.Logger + +class MongoTestResource : QuarkusTestResourceLifecycleManager { + companion object { + private val LOGGER: Logger = Logger.getLogger(MongoTestResource::class.java) + } + + private var mongod: MongodExecutable? = null + + override fun start(): Map { + return try { + val version: Version.Main = Version.Main.V4_0 + val port = 27018 + LOGGER.infof("Starting Mongo %s on port %s", version, port) + val config: IMongodConfig = MongodConfigBuilder() + .version(version) + .net(Net(port, Network.localhostIsIPv6())) + .build() + mongod = getMongodExecutable(config).also { + try { + it.start() + } catch (e: Exception) { + //every so often mongo fails to start on CI runs + //see if this helps + Thread.sleep(1000) + it.start() + } + + } + mapOf() + } catch (e: Exception) { + throw RuntimeException(e) + } + } + + private fun getMongodExecutable(config: IMongodConfig): MongodExecutable { + return try { + doGetExecutable(config) + } catch (e: Exception) { + // sometimes the download process can timeout so just sleep and try again + try { + Thread.sleep(1000) + } catch (ignored: InterruptedException) { + } + doGetExecutable(config) + } + } + + private fun doGetExecutable(config: IMongodConfig): MongodExecutable { + return MongodStarter.getDefaultInstance().prepare(config) + } + + override fun stop() { + mongod?.stop() + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/MongodbPanacheMockingTest.kt b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/MongodbPanacheMockingTest.kt new file mode 100644 index 0000000000000..2fe71cd4e42b8 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/MongodbPanacheMockingTest.kt @@ -0,0 +1,107 @@ +package io.quarkus.it.mongodb.panache + +import io.quarkus.it.mongodb.panache.person.MockablePersonRepository +import io.quarkus.it.mongodb.panache.person.Person +import io.quarkus.it.mongodb.panache.person.PersonEntity +import io.quarkus.it.mongodb.panache.person.PersonRepository +import io.quarkus.mongodb.panache.kotlin.PanacheMongoRepositoryBase +import io.quarkus.test.common.QuarkusTestResource +import io.quarkus.test.junit.QuarkusTest +import io.quarkus.test.junit.mockito.InjectMock +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.mockito.Mockito +import java.util.Collections +import javax.inject.Inject +import javax.ws.rs.WebApplicationException + +@QuarkusTest +@QuarkusTestResource(MongoTestResource::class) +class MongodbPanacheMockingTest { + @Inject + lateinit var realPersonRepository: PersonRepository + + @InjectMock + lateinit var mockablePersonRepository: MockablePersonRepository + + // These mocks are trying to call methods against instances but kotlin doesn't allow these kinds of method calls. + // they must be called against the companion object (invoked against the type not a reference). removing these tests + // for now as I neither know how to correct this at this point nor am I convinced of the utility of these tests. +/* + @Test + @Order(1) + fun testPanacheMocking() { + PanacheMock.mock(PersonEntity::class.java) + Assertions.assertEquals(0, PersonEntity.count()) + Mockito.`when`(PersonEntity.count()).thenReturn(23L) + Assertions.assertEquals(23, PersonEntity.count()) + Mockito.`when`(PersonEntity.count()).thenReturn(42L) + Assertions.assertEquals(42, PersonEntity.count()) + Mockito.`when`(PersonEntity.count()).thenCallRealMethod() + Assertions.assertEquals(0, PersonEntity.count()) + Assertions.assertEquals(4, PersonEntity.count()) + + val p = PersonEntity() + Mockito.`when`(PersonEntity.findById(12L)).thenReturn(p) + Assertions.assertSame(p, PersonEntity.findById(12L)) + Assertions.assertNull(PersonEntity.findById(42L)) + Mockito.`when`(PersonEntity.findById(12L)).thenThrow(WebApplicationException()) + try { + PersonEntity.findById(12L) + Assertions.fail() + } catch (x: WebApplicationException) { + } + Mockito.`when`(PersonEntity.findOrdered()).thenReturn(Collections.emptyList()) + Assertions.assertTrue(PersonEntity.findOrdered().isEmpty()) + PanacheMock.verify(PersonEntity::class.java).findOrdered() + PanacheMock.verify(PersonEntity::class.java, Mockito.atLeastOnce()).findById(Mockito.any()) + PanacheMock.verifyNoMoreInteractions(PersonEntity::class.java) + } + + @Test + @Order(2) + fun testPanacheMockingWasCleared() { + Assertions.assertFalse(PanacheMock.IsMockEnabled) + } +*/ + + @Test + @Throws(Throwable::class) + fun testPanacheRepositoryMocking() { + Assertions.assertEquals(0, mockablePersonRepository.count()) + Mockito.`when`(mockablePersonRepository.count()).thenReturn(23L) + Assertions.assertEquals(23, mockablePersonRepository.count()) + Mockito.`when`(mockablePersonRepository.count()).thenReturn(42L) + Assertions.assertEquals(42, mockablePersonRepository.count()) + Mockito.`when`(mockablePersonRepository.count()).thenCallRealMethod() + Assertions.assertEquals(0, mockablePersonRepository.count()) + Mockito.verify(mockablePersonRepository, Mockito.times(4)).count() + val p = PersonEntity() + Mockito.`when`(mockablePersonRepository.findById(12L)).thenReturn(p) + Assertions.assertSame(p, mockablePersonRepository.findById(12L)) + Assertions.assertNull(mockablePersonRepository.findById(42L)) + Mockito.`when`(mockablePersonRepository.findById(12L)).thenThrow(WebApplicationException()) + try { + mockablePersonRepository.findById(12L) + Assertions.fail() + } catch (x: WebApplicationException) { + } + Mockito.`when`(mockablePersonRepository.findOrdered()).thenReturn(Collections.emptyList()) + Assertions.assertTrue(mockablePersonRepository.findOrdered()?.isEmpty() ?: false) + Mockito.verify(mockablePersonRepository).findOrdered() + Mockito.verify(mockablePersonRepository, Mockito.atLeastOnce()).findById(12L) + } + + @Test + fun testPanacheRepositoryBridges() { + // normal method call + Assertions.assertNull(realPersonRepository.findById(0L)) + // bridge call + Assertions.assertNull((realPersonRepository as PanacheMongoRepositoryBase).findById(0L)) + + // normal method call + Assertions.assertEquals(false, realPersonRepository.deleteById(0L)) + // bridge call + Assertions.assertEquals(false, (realPersonRepository as PanacheMongoRepositoryBase).deleteById(0L)) + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.kt b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.kt new file mode 100644 index 0000000000000..8eaf4a5751b14 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.kt @@ -0,0 +1,333 @@ +package io.quarkus.it.mongodb.panache + +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.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.RestAssured.get +import io.restassured.common.mapper.TypeRef +import io.restassured.config.ObjectMapperConfig +import io.restassured.parsing.Parser +import io.restassured.response.Response +import org.hamcrest.Matchers.`is` +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import java.util.Calendar +import java.util.Collections +import java.util.Date +import java.util.GregorianCalendar + +@QuarkusTest +@QuarkusTestResource(MongoTestResource::class) +open class MongodbPanacheResourceTest { + @Test + fun testBookEntity() { + callBookEndpoint("/books/entity") + } + + @Test + fun testBookRepository() { + callBookEndpoint("/books/repository") + } + + @Test + fun testPersonEntity() { + callPersonEndpoint("/persons/entity") + } + + @Test + fun testPersonRepository() { + callPersonEndpoint("/persons/repository") + } + + private fun callBookEndpoint(endpoint: String) { + RestAssured.defaultParser = Parser.JSON + RestAssured.config + .objectMapperConfig(ObjectMapperConfig().jackson2ObjectMapperFactory { _, _ -> + ObjectMapper() + .registerModule(Jdk8Module()) + .registerModule(JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + }) + var list: List = get(endpoint).`as`(LIST_OF_BOOK_TYPE_REF) + Assertions.assertEquals(0, list.size) + val book1: BookDTO = BookDTO().setAuthor("Victor Hugo").setTitle("Les Misérables") + .setCreationDate(yearToDate(1886)) + .setCategories(listOf("long", "very long")) + .setDetails(BookDetail().setRating(3).setSummary("A very long book")) + var response: Response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book1) + .post(endpoint) + .andReturn() + Assertions.assertEquals(201, response.statusCode()) + Assertions.assertTrue(response.header("Location").length > 20) //Assert that id has been populated + val book2: BookDTO = BookDTO().setAuthor("Victor Hugo").setTitle("Notre-Dame de Paris") + .setCreationDate(yearToDate(1831)) + .setCategories(listOf("long", "quasimodo")) + .setDetails(BookDetail().setRating(4).setSummary("quasimodo and esmeralda")) + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book2) + .post(endpoint) + .andReturn() + Assertions.assertEquals(201, response.statusCode()) + list = get(endpoint).`as`(LIST_OF_BOOK_TYPE_REF) + Assertions.assertEquals(2, list.size) + val book3: BookDTO = BookDTO().setAuthor("Charles Baudelaire").setTitle("Les fleurs du mal") + .setCreationDate(yearToDate(1857)) + .setCategories(Collections.singletonList("poem")) + .setDetails(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() + Assertions.assertEquals(201, response.statusCode()) + val book4: BookDTO = BookDTO().setAuthor("Charles Baudelaire").setTitle("Le Spleen de Paris") + .setCreationDate(yearToDate(1869)) + .setCategories(Collections.singletonList("poem")) + .setDetails(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() + Assertions.assertEquals(202, response.statusCode()) + list = get(endpoint).`as`(LIST_OF_BOOK_TYPE_REF) + Assertions.assertEquals(4, list.size) + + //with sort + list = get("$endpoint?sort=author").`as`(LIST_OF_BOOK_TYPE_REF) + Assertions.assertEquals(4, list.size) + + // magic query find("author", author) + list = get("$endpoint/search/Victor Hugo").`as`(LIST_OF_BOOK_TYPE_REF) + Assertions.assertEquals(2, list.size) + // we have a projection so we should not have the details field but we should have the title thanks to @BsonProperty + Assertions.assertNotNull(list[0].title) + Assertions.assertNull(list[0].details) + + // magic query find("{'author':?1,'title':?1}", author, title) + var book: BookDTO = get("$endpoint/search?author=Victor Hugo&title=Notre-Dame de Paris").`as`(BookDTO::class.java) + Assertions.assertNotNull(book) + + // date + book = get("$endpoint/search?dateFrom=1885-01-01&dateTo=1887-01-01").`as`(BookDTO::class.java) + Assertions.assertNotNull(book) + book = get("$endpoint/search2?dateFrom=1885-01-01&dateTo=1887-01-01").`as`(BookDTO::class.java) + Assertions.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.java) + Assertions.assertNotNull(book) + Assertions.assertNotNull(book.id) + Assertions.assertNotNull(book.details) + + //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() + Assertions.assertEquals(202, response.statusCode()) + + //check that the title has been updated and the transient description ignored + book = get(endpoint + "/" + book.id.toString()).`as`(BookDTO::class.java) + Assertions.assertNotNull(book) + Assertions.assertEquals("Notre-Dame de Paris 2", book.title) + Assertions.assertNull(book.transientDescription) + + //delete a book + response = RestAssured + .given() + .delete(endpoint + "/" + book.id.toString()) + .andReturn() + Assertions.assertEquals(204, response.statusCode()) + list = get(endpoint).`as`(LIST_OF_BOOK_TYPE_REF) + Assertions.assertEquals(3, list.size) + + //test some special characters + list = get("$endpoint/search/Victor'\\ Hugo").`as`(LIST_OF_BOOK_TYPE_REF) + Assertions.assertEquals(0, list.size) + + //delete all + response = RestAssured + .given() + .delete(endpoint) + .andReturn() + Assertions.assertEquals(204, response.statusCode()) + } + + private fun callPersonEndpoint(endpoint: String) { + RestAssured.defaultParser = Parser.JSON + RestAssured.config + .objectMapperConfig(ObjectMapperConfig().jackson2ObjectMapperFactory { _, _ -> + ObjectMapper() + .registerModule(Jdk8Module()) + .registerModule(JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + }) + var list: List = get(endpoint).`as`(LIST_OF_PERSON_TYPE_REF) + Assertions.assertEquals(0, list.size) + val person1 = Person() + person1.id = 1L + person1.firstname = "John" + person1.lastname = "Doe" + var response: Response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(person1) + .post(endpoint) + .andReturn() + Assertions.assertEquals(201, response.statusCode()) + val person2 = Person() + person2.id = 2L + person2.firstname = "Jane" + person2.lastname = "Doe" + var person3 = Person() + person3.id = 3L + person3.firstname = "Victor" + person3.lastname = "Hugo" + val persons = mutableListOf() + persons.add(person2) + persons.add(person3) + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(persons) + .post("$endpoint/multiple") + .andReturn() + Assertions.assertEquals(204, response.statusCode()) + val person4 = Person() + person4.id = 4L + person4.firstname = "Charles" + person4.lastname = "Baudelaire" + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(person4) + .patch(endpoint) + .andReturn() + Assertions.assertEquals(202, response.statusCode()) + list = get(endpoint).`as`(LIST_OF_PERSON_TYPE_REF) + Assertions.assertEquals(4, list.size) + + //with sort + list = get("$endpoint?sort=firstname").`as`(LIST_OF_PERSON_TYPE_REF) + Assertions.assertEquals(4, list.size) + + //with project + list = get("$endpoint/search/Doe").`as`(LIST_OF_PERSON_TYPE_REF) + Assertions.assertEquals(1, list.size) + Assertions.assertNotNull(list[0].lastname) + //expected the firstname field to be null as we project on lastname only + Assertions.assertNull(list[0].firstname) + + //rename the Doe + RestAssured + .given() + .queryParam("previousName", "Doe").queryParam("newName", "Dupont") + .header("Content-Type", "application/json") + .`when`().post("$endpoint/rename") + .then().statusCode(200) + list = get("$endpoint/search/Dupont").`as`(LIST_OF_PERSON_TYPE_REF) + Assertions.assertEquals(1, list.size) + + //count + var count: Long = get("$endpoint/count").`as`(Long::class.java) + Assertions.assertEquals(4, count) + + //update a person + person3.lastname = "Webster" + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(person3) + .put(endpoint) + .andReturn() + Assertions.assertEquals(202, response.statusCode()) + + //check that the title has been updated + person3 = get("$endpoint/${person3.id}").`as`(Person::class.java) + Assertions.assertEquals(3L, person3.id ?: -1) + Assertions.assertEquals("Webster", person3.lastname) + + //delete a person + response = RestAssured + .given() + .delete("$endpoint/${person3.id}") + .andReturn() + Assertions.assertEquals(204, response.statusCode()) + count = get("$endpoint/count").`as`(Long::class.java) + Assertions.assertEquals(3, count) + + //delete all + response = RestAssured + .given() + .delete(endpoint) + .andReturn() + Assertions.assertEquals(204, response.statusCode()) + count = get("$endpoint/count").`as`(Long::class.java) + Assertions.assertEquals(0, count) + } + + private fun yearToDate(year: Int): Date { + val cal: Calendar = GregorianCalendar() + cal.set(year, 1, 1) + return cal.time + } + + @Test + fun testBug5274() { + get("/bugs/5274").then().body(`is`("OK")) + } + + @Test + fun testBug5885() { + get("/bugs/5885").then().body(`is`("OK")) + } + + @Test + fun testDatesFormat() { + get("/bugs/dates").then().statusCode(200) + } + + @Test + fun testNeedReflection() { + get("/bugs/6324").then().statusCode(200) + get("/bugs/6324/abstract").then().statusCode(200) + } + + @Test + fun testBug7415() { + get("/bugs/7415").then().statusCode(200) + } + + @Test + fun testMoreEntityFunctionalities() { + get("/test/imperative/entity").then().statusCode(200) + } + + @Test + fun testMoreRepositoryFunctionalities() { + get("/test/imperative/repository").then().statusCode(200) + } + + companion object { + private val LIST_OF_BOOK_TYPE_REF: TypeRef> = object : TypeRef>() {} + private val LIST_OF_PERSON_TYPE_REF: TypeRef> = object : TypeRef>() {} + } +} \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/reactive/NativeMongodbPanacheResourceIT.kt b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/reactive/NativeMongodbPanacheResourceIT.kt new file mode 100644 index 0000000000000..a688929f09b4d --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/reactive/NativeMongodbPanacheResourceIT.kt @@ -0,0 +1,7 @@ +package io.quarkus.it.mongodb.panache.reactive + +import io.quarkus.it.mongodb.panache.MongodbPanacheResourceTest +import io.quarkus.test.junit.NativeImageTest + +@NativeImageTest +internal class NativeMongodbPanacheResourceIT : MongodbPanacheResourceTest() \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/reactive/NativeReactiveMongodbPanacheResourceIT.kt b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/reactive/NativeReactiveMongodbPanacheResourceIT.kt new file mode 100644 index 0000000000000..fe3c384072837 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/reactive/NativeReactiveMongodbPanacheResourceIT.kt @@ -0,0 +1,6 @@ +package io.quarkus.it.mongodb.panache.reactive + +import io.quarkus.test.junit.NativeImageTest + +@NativeImageTest +internal class NativeReactiveMongodbPanacheResourceIT : ReactiveMongodbPanacheResourceTest() \ No newline at end of file diff --git a/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/reactive/ReactiveMongodbPanacheResourceTest.kt b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/reactive/ReactiveMongodbPanacheResourceTest.kt new file mode 100644 index 0000000000000..7221fb752add1 --- /dev/null +++ b/integration-tests/mongodb-panache-kotlin/src/test/kotlin/io/quarkus/it/mongodb/panache/reactive/ReactiveMongodbPanacheResourceTest.kt @@ -0,0 +1,351 @@ +package io.quarkus.it.mongodb.panache.reactive + +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.RestAssured.get +import io.restassured.common.mapper.TypeRef +import io.restassured.config.ObjectMapperConfig +import io.restassured.parsing.Parser +import io.restassured.response.Response +import org.awaitility.Awaitility.await +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Test +import java.io.IOException +import java.time.Duration +import java.util.Calendar +import java.util.Collections +import java.util.Date +import java.util.GregorianCalendar +import javax.ws.rs.client.Client +import javax.ws.rs.client.ClientBuilder +import javax.ws.rs.client.WebTarget +import javax.ws.rs.sse.SseEventSource + +@QuarkusTest +@QuarkusTestResource(MongoTestResource::class) +internal open class ReactiveMongodbPanacheResourceTest { + companion object { + private val LIST_OF_BOOK_TYPE_REF: TypeRef> = object : TypeRef>() {} + private val LIST_OF_PERSON_TYPE_REF: TypeRef> = object : TypeRef>() {} + } + + @Test + @Throws(InterruptedException::class) + fun testReactiveBookEntity() { + callReactiveBookEndpoint("/reactive/books/entity") + } + + @Test + @Throws(InterruptedException::class) + fun testReactiveBookRepository() { + callReactiveBookEndpoint("/reactive/books/repository") + } + + @Test + fun testReactivePersonEntity() { + callReactivePersonEndpoint("/reactive/persons/entity") + } + + @Test + fun testReactivePersonRepository() { + callReactivePersonEndpoint("/reactive/persons/repository") + } + + @Throws(InterruptedException::class) + private fun callReactiveBookEndpoint(endpoint: String) { + RestAssured.defaultParser = Parser.JSON + val objectMapper: ObjectMapper = ObjectMapper() + .registerModule(Jdk8Module()) + .registerModule(JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + RestAssured.config + .objectMapperConfig(ObjectMapperConfig().jackson2ObjectMapperFactory { _, _ -> objectMapper }) + + var list: List = get(endpoint).`as`(LIST_OF_BOOK_TYPE_REF) + assertEquals(0, list.size) + + val book1: BookDTO = BookDTO().setAuthor("Victor Hugo").setTitle("Les Misérables") + .setCreationDate(yearToDate(1886)) + .setCategories(listOf("long", "very long")) + .setDetails(BookDetail().setRating(3).setSummary("A very long book")) + var 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 + + val book2: BookDTO = BookDTO().setAuthor("Victor Hugo").setTitle("Notre-Dame de Paris") + .setCreationDate(yearToDate(1831)) + .setCategories(listOf("long", "quasimodo")) + .setDetails(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) + + val book3: BookDTO = BookDTO().setAuthor("Charles Baudelaire").setTitle("Les fleurs du mal") + .setCreationDate(yearToDate(1857)) + .setCategories(Collections.singletonList("poem")) + .setDetails(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()) + + val book4: BookDTO = BookDTO().setAuthor("Charles Baudelaire").setTitle("Le Spleen de Paris") + .setCreationDate(yearToDate(1869)) + .setCategories(Collections.singletonList("poem")) + .setDetails(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) + var book: BookDTO = get("$endpoint/search?author=Victor Hugo&title=Notre-Dame de Paris").`as`(BookDTO::class.java) + assertNotNull(book) + + // date + book = get("$endpoint/search?dateFrom=1885-01-01&dateTo=1887-01-01").`as`(BookDTO::class.java) + assertNotNull(book) + + book = get("$endpoint/search2?dateFrom=1885-01-01&dateTo=1887-01-01").`as`(BookDTO::class.java) + 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.java) + assertNotNull(book) + assertNotNull(book.id) + assertNotNull(book.details) + + //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.id.toString()).`as`(BookDTO::class.java) + assertEquals("Notre-Dame de Paris 2", book.title) + Assertions.assertNull(book.transientDescription) + + //delete a book + response = RestAssured + .given() + .delete(endpoint + "/" + book.id.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 ... + val client: Client = ClientBuilder.newClient() + val target: WebTarget = client.target("http://localhost:8081$endpoint/stream") + SseEventSource.target(target).build().use { source -> + val nbEvent = IntegerAdder() + source.register { inboundSseEvent -> + try { + val theBook: BookDTO = objectMapper.readValue(inboundSseEvent.readData(), BookDTO::class.java) + assertNotNull(theBook) + } catch (e: IOException) { + throw RuntimeException(e) + } + nbEvent.increment() + } + source.open() + await().atMost(Duration.ofSeconds(1)).until({ nbEvent.count() == 3 }) + } + + //delete all + response = RestAssured + .given() + .delete(endpoint) + .andReturn() + assertEquals(204, response.statusCode()) + } + + private fun callReactivePersonEndpoint(endpoint: String) { + RestAssured.defaultParser = Parser.JSON + RestAssured.config + .objectMapperConfig(ObjectMapperConfig().jackson2ObjectMapperFactory({ type, s -> + ObjectMapper() + .registerModule(Jdk8Module()) + .registerModule(JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) + })) + var list: List = get(endpoint).`as`(LIST_OF_PERSON_TYPE_REF) + assertEquals(0, list.size) + val person1 = Person() + person1.id = 1L + person1.firstname = "John" + person1.lastname = "Doe" + var response: Response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(person1) + .post(endpoint) + .andReturn() + assertEquals(201, response.statusCode()) + val person2 = Person() + person2.id = 2L + person2.firstname = "Jane" + person2.lastname = "Doe" + var person3 = Person() + person3.id = 3L + person3.firstname = "Victor" + person3.lastname = "Hugo" + val persons = mutableListOf() + persons.add(person2) + persons.add(person3) + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(persons) + .post("$endpoint/multiple") + .andReturn() + assertEquals(204, response.statusCode()) + val person4 = 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) + + //with project + list = get("$endpoint/search/Doe").`as`(LIST_OF_PERSON_TYPE_REF) + assertEquals(1, list.size) + assertNotNull(list[0].lastname) + //expected the firstname field to be null as we project on lastname only + Assertions.assertNull(list[0].firstname) + + //rename the Doe + RestAssured + .given() + .queryParam("previousName", "Doe").queryParam("newName", "Dupont") + .header("Content-Type", "application/json") + .`when`().post("$endpoint/rename") + .then().statusCode(200) + list = get("$endpoint/search/Dupont").`as`(LIST_OF_PERSON_TYPE_REF) + assertEquals(1, list.size) + + //count + var count: Long = get("$endpoint/count").`as`(Long::class.java) + 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}").`as`(Person::class.java) + assertEquals(3L, person3.id ?: -1L) + assertEquals("Webster", person3.lastname) + + //delete a person + response = RestAssured + .given() + .delete(endpoint + "/" + person3.id) + .andReturn() + assertEquals(204, response.statusCode()) + count = get("$endpoint/count").`as`(Long::class.java) + assertEquals(3, count) + + //delete all + response = RestAssured + .given() + .delete(endpoint) + .andReturn() + assertEquals(204, response.statusCode()) + count = get("$endpoint/count").`as`(Long::class.java) + assertEquals(0, count) + } + + private fun yearToDate(year: Int): Date { + val cal: Calendar = GregorianCalendar() + cal.set(year, 1, 1) + return cal.getTime() + } + + private class IntegerAdder { + var cpt = 0 + fun increment() { + cpt++ + } + + fun count(): Int = cpt + } + + @Test + fun testMoreEntityFunctionalities() { + get("/test/reactive/entity").then().statusCode(200) + } + + @Test + fun testMoreRepositoryFunctionalities() { + get("/test/reactive/repository").then().statusCode(200) + } +} \ No newline at end of file diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 5aba651740779..b30942574b4d0 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -104,6 +104,7 @@ scala kotlin mongodb-panache + mongodb-panache-kotlin narayana-stm narayana-jta elytron-security-jdbc