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 extends EntityModel extends EntityFieldType>> 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());
+ }
}