diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml index 9062988bd45927..8b1d7ab6313f77 100644 --- a/bom/deployment/pom.xml +++ b/bom/deployment/pom.xml @@ -351,6 +351,11 @@ quarkus-panache-common-deployment ${project.version} + + io.quarkus + quarkus-panache-mock-deployment + ${project.version} + io.quarkus quarkus-mongodb-panache-deployment diff --git a/bom/runtime/pom.xml b/bom/runtime/pom.xml index 11c8f780374f30..0018729e46e600 100644 --- a/bom/runtime/pom.xml +++ b/bom/runtime/pom.xml @@ -409,6 +409,11 @@ quarkus-panache-common ${project.version} + + io.quarkus + quarkus-panache-mock + ${project.version} + io.quarkus quarkus-panacheql diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java index 92da1114320c80..346aa211f25796 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/FeatureBuildItem.java @@ -54,6 +54,7 @@ public final class FeatureBuildItem extends MultiBuildItem { public static final String OIDC = "oidc"; public static final String OPTAPLANNER = "optaplanner"; public static final String OPTAPLANNER_JACKSON = "optaplanner-jackson"; + public static final String PANACHE_MOCK = "panache-mock"; public static final String QUTE = "qute"; public static final String RESTEASY = "resteasy"; public static final String RESTEASY_JACKSON = "resteasy-jackson"; 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 f29c14ad090c2d..7e3135519826cb 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 @@ -1,6 +1,7 @@ package io.quarkus.hibernate.orm.panache.deployment; import java.lang.reflect.Modifier; +import java.util.List; import javax.persistence.Transient; @@ -22,6 +23,7 @@ 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 PanacheJpaEntityEnhancer extends PanacheEntityEnhancer>> { @@ -38,15 +40,15 @@ public class PanacheJpaEntityEnhancer extends PanacheEntityEnhancer methodCustomizers) { + super(index, PanacheResourceProcessor.DOTNAME_PANACHE_ENTITY_BASE, methodCustomizers); modelInfo = new MetamodelInfo<>(); } @Override public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { return new PanacheJpaEntityClassVisitor(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, - indexView.getClassByName(DotName.createSimple(className))); + indexView.getClassByName(DotName.createSimple(className)), methodCustomizers); } static class PanacheJpaEntityClassVisitor extends PanacheEntityClassVisitor { @@ -54,8 +56,9 @@ static class PanacheJpaEntityClassVisitor extends PanacheEntityClassVisitor> modelInfo, ClassInfo panacheEntityBaseClassInfo, - ClassInfo entityInfo) { - super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo); + ClassInfo entityInfo, + List methodCustomizers) { + super(className, outputClassVisitor, modelInfo, panacheEntityBaseClassInfo, entityInfo, methodCustomizers); } @Override diff --git a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java index 523a787771c6b6..123fae7e8154a4 100644 --- a/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java +++ b/extensions/panache/hibernate-orm-panache/deployment/src/main/java/io/quarkus/hibernate/orm/panache/deployment/PanacheResourceProcessor.java @@ -4,6 +4,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import javax.persistence.EntityManager; @@ -27,6 +28,8 @@ import io.quarkus.panache.common.deployment.MetamodelInfo; import io.quarkus.panache.common.deployment.PanacheEntityClassesBuildItem; 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 PanacheResourceProcessor { @@ -60,7 +63,11 @@ UnremovableBeanBuildItem ensureBeanLookupAvailable() { void build(CombinedIndexBuildItem index, BuildProducer transformers, HibernateEnhancersRegisteredBuildItem hibernateMarker, - BuildProducer entityClasses) throws Exception { + BuildProducer entityClasses, + List methodCustomizersBuildItems) throws Exception { + + List methodCustomizers = methodCustomizersBuildItems.stream() + .map(bi -> bi.getMethodCustomizer()).collect(Collectors.toList()); PanacheJpaRepositoryEnhancer daoEnhancer = new PanacheJpaRepositoryEnhancer(index.getIndex()); Set daoClasses = new HashSet<>(); @@ -81,7 +88,7 @@ void build(CombinedIndexBuildItem index, transformers.produce(new BytecodeTransformerBuildItem(daoClass, daoEnhancer)); } - PanacheJpaEntityEnhancer modelEnhancer = new PanacheJpaEntityEnhancer(index.getIndex()); + PanacheJpaEntityEnhancer modelEnhancer = new PanacheJpaEntityEnhancer(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 PanacheEntity if we ask for subtypes of PanacheEntityBase diff --git a/extensions/panache/hibernate-orm-panache/runtime/pom.xml b/extensions/panache/hibernate-orm-panache/runtime/pom.xml index 619864a3a1296e..db189f7b4f32dd 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/pom.xml +++ b/extensions/panache/hibernate-orm-panache/runtime/pom.xml @@ -39,6 +39,16 @@ quarkus-jackson true + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-core + test + org.hibernate diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/JandexUtil.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/JandexUtil.java index 5a1359ed69f361..a264d4a19a2e64 100644 --- a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/JandexUtil.java +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/JandexUtil.java @@ -2,6 +2,7 @@ import static java.util.stream.Collectors.toList; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Function; @@ -13,7 +14,9 @@ import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.PrimitiveType.Primitive; import org.jboss.jandex.Type; +import org.jboss.jandex.Type.Kind; import org.jboss.jandex.TypeVariable; +import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import io.quarkus.panache.common.impl.GenerateBridge; @@ -57,6 +60,12 @@ public static String getDescriptor(MethodInfo method, Function t return descriptor.toString(); } + public static String getDescriptor(Type type, Function typeArgMapper) { + StringBuilder sb = new StringBuilder(); + toSignature(sb, type, typeArgMapper, true); + return sb.toString(); + } + static void toSignature(StringBuilder sb, Type type, Function typeArgMapper, boolean erased) { switch (type.kind()) { case ARRAY: @@ -163,6 +172,30 @@ public static int getReturnInstruction(String typeDescriptor) { } } + public static int getReturnInstruction(Type jandexType) { + if (jandexType.kind() == Kind.PRIMITIVE) { + switch (jandexType.asPrimitiveType().primitive()) { + case BOOLEAN: + case BYTE: + case SHORT: + case INT: + case CHAR: + return Opcodes.IRETURN; + case DOUBLE: + return Opcodes.DRETURN; + case FLOAT: + return Opcodes.FRETURN; + case LONG: + return Opcodes.LRETURN; + default: + throw new IllegalArgumentException("Unknown primitive type: " + jandexType); + } + } else if (jandexType.kind() == Kind.VOID) { + return Opcodes.RETURN; + } + return Opcodes.ARETURN; + } + /** * Checks if the {@link ClassInfo} contains a method * @@ -201,4 +234,203 @@ public static boolean containsMethod(ClassInfo classInfo, MethodInfo methodInfo) } return false; } + + public static void visitLdc(MethodVisitor mv, Type jandexType) { + switch (jandexType.kind()) { + case ARRAY: + mv.visitLdcInsn(org.objectweb.asm.Type.getType(jandexType.name().toString('/').replace('.', '/'))); + break; + case CLASS: + case PARAMETERIZED_TYPE: + mv.visitLdcInsn(org.objectweb.asm.Type.getType("L" + jandexType.name().toString('/') + ";")); + break; + case PRIMITIVE: + switch (jandexType.asPrimitiveType().primitive()) { + case BOOLEAN: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;"); + break; + case BYTE: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;"); + break; + case CHAR: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;"); + break; + case DOUBLE: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;"); + break; + case FLOAT: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;"); + break; + case INT: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;"); + break; + case LONG: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;"); + break; + case SHORT: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;"); + break; + default: + throw new IllegalArgumentException("Unknown primitive type: " + jandexType); + } + break; + case TYPE_VARIABLE: + List bounds = jandexType.asTypeVariable().bounds(); + if (bounds.isEmpty()) + mv.visitLdcInsn(org.objectweb.asm.Type.getType(Object.class)); + else + visitLdc(mv, bounds.get(0)); + break; + case UNRESOLVED_TYPE_VARIABLE: + mv.visitLdcInsn(org.objectweb.asm.Type.getType(Object.class)); + break; + case VOID: + mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/Void", "TYPE", "Ljava/lang/Class;"); + break; + case WILDCARD_TYPE: + visitLdc(mv, jandexType.asWildcardType().extendsBound()); + break; + default: + throw new IllegalArgumentException("Unknown jandex type: " + jandexType); + } + } + + public static void boxIfRequired(MethodVisitor mv, Type jandexType) { + if (jandexType.kind() == Kind.PRIMITIVE) { + switch (jandexType.asPrimitiveType().primitive()) { + case BOOLEAN: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); + break; + case BYTE: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false); + break; + case CHAR: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", + false); + break; + case DOUBLE: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); + break; + case FLOAT: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false); + break; + case INT: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); + break; + case LONG: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); + break; + case SHORT: + mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false); + break; + default: + throw new IllegalArgumentException("Unknown primitive type: " + jandexType); + } + } + } + + public static int getLoadOpcode(Type jandexType) { + if (jandexType.kind() == Kind.PRIMITIVE) { + switch (jandexType.asPrimitiveType().primitive()) { + case BOOLEAN: + case BYTE: + case SHORT: + case INT: + case CHAR: + return Opcodes.ILOAD; + case DOUBLE: + return Opcodes.DLOAD; + case FLOAT: + return Opcodes.FLOAD; + case LONG: + return Opcodes.LLOAD; + default: + throw new IllegalArgumentException("Unknown primitive type: " + jandexType); + } + } + return Opcodes.ALOAD; + } + + public static void unboxIfRequired(MethodVisitor mv, Type jandexType) { + if (jandexType.kind() == Kind.PRIMITIVE) { + switch (jandexType.asPrimitiveType().primitive()) { + case BOOLEAN: + unbox(mv, "java/lang/Boolean", "booleanValue", "Z"); + break; + case BYTE: + unbox(mv, "java/lang/Byte", "byteValue", "B"); + break; + case CHAR: + unbox(mv, "java/lang/Character", "charValue", "C"); + break; + case DOUBLE: + unbox(mv, "java/lang/Double", "doubleValue", "D"); + break; + case FLOAT: + unbox(mv, "java/lang/Float", "floatValue", "F"); + break; + case INT: + unbox(mv, "java/lang/Integer", "intValue", "I"); + break; + case LONG: + unbox(mv, "java/lang/Long", "longValue", "J"); + break; + case SHORT: + unbox(mv, "java/lang/Short", "shortValue", "S"); + break; + default: + throw new IllegalArgumentException("Unknown primitive type: " + jandexType); + } + } + } + + 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); + } + + public static Type[] getParameterTypes(String methodDescriptor) { + String argsSignature = methodDescriptor.substring(methodDescriptor.indexOf('(') + 1, methodDescriptor.lastIndexOf(')')); + List args = new ArrayList<>(); + char[] chars = argsSignature.toCharArray(); + int dimensions = 0; + int start = 0; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + switch (c) { + case 'Z': + case 'B': + case 'C': + case 'D': + case 'F': + case 'I': + case 'J': + case 'S': + args.add(Type.create(DotName.createSimple(argsSignature.substring(start, i + 1)), dimensions > 0 ? Kind.ARRAY : Kind.PRIMITIVE)); + dimensions = 0; + start = i + 1; + break; + case 'L': + int end = argsSignature.indexOf(';', i); + String binaryName = argsSignature.substring(i + 1, end); + // arrays take the entire signature + if (dimensions > 0) { + args.add(Type.create(DotName.createSimple(argsSignature.substring(start, end + 1)), Kind.ARRAY)); + dimensions = 0; + } else { + // class names take only the binary name + args.add(Type.create(DotName.createSimple(binaryName.replace('/', '.')), Kind.CLASS)); + } + i = end; // we will have a ++ to get after the ; + start = i + 1; + break; + case '[': + dimensions++; + break; + default: + throw new IllegalStateException("Invalid signature char: " + c); + } + } + return args.toArray(new Type[0]); + } } 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 947706411ef4d4..b5b0a8452a077b 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,5 +1,7 @@ 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; @@ -45,10 +47,13 @@ public abstract class PanacheEntityEnhancer methodCustomizers; - public PanacheEntityEnhancer(IndexView index, DotName panacheEntityBaseName) { + public PanacheEntityEnhancer(IndexView index, DotName panacheEntityBaseName, + List methodCustomizers) { this.panacheEntityBaseClassInfo = index.getClassByName(panacheEntityBaseName); this.indexView = index; + this.methodCustomizers = methodCustomizers; } @Override @@ -63,11 +68,13 @@ protected abstract static class PanacheEntityClassVisitor modelInfo; private ClassInfo panacheEntityBaseClassInfo; protected ClassInfo entityInfo; + protected List methodCustomizers; public PanacheEntityClassVisitor(String className, ClassVisitor outputClassVisitor, MetamodelInfo> modelInfo, ClassInfo panacheEntityBaseClassInfo, - ClassInfo entityInfo) { + ClassInfo entityInfo, + List methodCustomizers) { super(Gizmo.ASM_API_VERSION, outputClassVisitor); thisClass = Type.getType("L" + className.replace('.', '/') + ";"); this.modelInfo = modelInfo; @@ -75,6 +82,7 @@ public PanacheEntityClassVisitor(String className, ClassVisitor outputClassVisit fields = entityModel != null ? entityModel.fields : null; this.panacheEntityBaseClassInfo = panacheEntityBaseClassInfo; this.entityInfo = entityInfo; + this.methodCustomizers = methodCustomizers; } @Override @@ -124,6 +132,17 @@ public MethodVisitor visitMethod(int access, String methodName, String descripto methods.add(methodName + "/" + descriptor); } MethodVisitor superVisitor = super.visitMethod(access, methodName, descriptor, signature, exceptions); + if(Modifier.isStatic(access) + && Modifier.isPublic(access) + && (access & Opcodes.ACC_SYNTHETIC) == 0) { + org.jboss.jandex.Type[] argTypes = JandexUtil.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); } @@ -165,6 +184,9 @@ private void generateMethod(MethodInfo method, AnnotationValue targetReturnTypeE 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++) { diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizer.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizer.java new file mode 100644 index 00000000000000..bf9a86b488bf8e --- /dev/null +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizer.java @@ -0,0 +1,11 @@ +package io.quarkus.panache.common.deployment; + +import org.jboss.jandex.MethodInfo; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +public interface PanacheMethodCustomizer { + + public void customize(Type entityClassSignature, MethodInfo method, MethodVisitor mv); + +} diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerBuildItem.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerBuildItem.java new file mode 100644 index 00000000000000..293d6cf78669ab --- /dev/null +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerBuildItem.java @@ -0,0 +1,18 @@ +package io.quarkus.panache.common.deployment; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Build item to declare that a {@link PanacheMethodCustomizer} should be used on Panache-enhanced methods. + */ +public final class PanacheMethodCustomizerBuildItem extends MultiBuildItem { + private PanacheMethodCustomizer methodCustomizer; + + public PanacheMethodCustomizerBuildItem(PanacheMethodCustomizer methodCustomizer) { + this.methodCustomizer = methodCustomizer; + } + + public PanacheMethodCustomizer getMethodCustomizer() { + return methodCustomizer; + } +} diff --git a/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerVisitor.java b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerVisitor.java new file mode 100644 index 00000000000000..21403b660db00f --- /dev/null +++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerVisitor.java @@ -0,0 +1,31 @@ +package io.quarkus.panache.common.deployment; + +import java.util.List; + +import org.jboss.jandex.MethodInfo; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +import io.quarkus.gizmo.Gizmo; + +public class PanacheMethodCustomizerVisitor extends MethodVisitor { + + private List methodCustomizers; + private MethodInfo method; + private Type thisClass; + + public PanacheMethodCustomizerVisitor(MethodVisitor superVisitor, MethodInfo method, Type thisClass, List methodCustomizers) { + super(Gizmo.ASM_API_VERSION, superVisitor); + this.thisClass = thisClass; + this.method = method; + this.methodCustomizers = methodCustomizers; + } + + @Override + public void visitCode() { + super.visitCode(); + for (PanacheMethodCustomizer customizer : methodCustomizers) { + customizer.customize(thisClass, method, mv); + } + } +} diff --git a/extensions/panache/pom.xml b/extensions/panache/pom.xml index 34220535ae0420..8a36507c66f7fe 100644 --- a/extensions/panache/pom.xml +++ b/extensions/panache/pom.xml @@ -15,6 +15,7 @@ pom panache-common + panache-mock hibernate-orm-panache mongodb-panache panacheql diff --git a/integration-tests/hibernate-orm-panache/pom.xml b/integration-tests/hibernate-orm-panache/pom.xml index 9006bfdc8c3684..183f3f7c413adc 100644 --- a/integration-tests/hibernate-orm-panache/pom.xml +++ b/integration-tests/hibernate-orm-panache/pom.xml @@ -78,6 +78,19 @@ quarkus-jackson test + + io.quarkus + quarkus-panache-mock + + + + net.bytebuddy + byte-buddy + + + com.fasterxml.jackson.module jackson-module-jaxb-annotations diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java index add79fb5bd40ff..86fb15980aa20f 100644 --- a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/PanacheFunctionalityTest.java @@ -4,19 +4,24 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.StringWriter; +import java.util.Collections; import javax.json.bind.Jsonb; import javax.json.bind.JsonbBuilder; +import javax.ws.rs.WebApplicationException; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import io.quarkus.panache.mock.PanacheMock; import io.quarkus.test.junit.DisabledOnNativeImage; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; @@ -163,4 +168,42 @@ public void testBug7721() { public void testBug8254() { RestAssured.when().get("/test/8254").then().body(is("OK")); } + + @AfterEach + public void afterEach() { + PanacheMock.reset(); + } + + @DisabledOnNativeImage + @Test + public void testPanacheMocking() { + PanacheMock.mock(Person.class); + + Assertions.assertEquals(0, Person.count()); + + Mockito.when(Person.count()).thenReturn(23l); + Assertions.assertEquals(23, Person.count()); + + Mockito.when(Person.count()).thenReturn(42l); + Assertions.assertEquals(42, Person.count()); + + Mockito.when(Person.count()).thenCallRealMethod(); + Assertions.assertEquals(0, Person.count()); + + Mockito.verify(PanacheMock.getMock(Person.class), Mockito.times(4)).count(); + + Mockito.when(Person.findById(12l)).thenReturn(p); + Assertions.assertSame(p, Person.findById(12l)); + Assertions.assertNull(Person.findById(42l)); + + Mockito.when(Person.findById(12l)).thenThrow(new WebApplicationException()); + try { + Person.findById(12l); + Assertions.fail(); + } catch (WebApplicationException x) { + } + + Mockito.when(Person.findOrdered()).thenReturn(Collections.emptyList()); + Assertions.assertTrue(Person.findOrdered().isEmpty()); + } }