diff --git a/bom/deployment/pom.xml b/bom/deployment/pom.xml
index ce1b0648d061c..c6a9f811fb2c4 100644
--- a/bom/deployment/pom.xml
+++ b/bom/deployment/pom.xml
@@ -373,6 +373,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 b7754afebe918..048cdc23c9403 100644
--- a/bom/runtime/pom.xml
+++ b/bom/runtime/pom.xml
@@ -425,6 +425,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 92da1114320c8..346aa211f2579 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 f29c14ad090c2..7e3135519826c 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 523a787771c6b..123fae7e8154a 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 619864a3a1296..db189f7b4f32d 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/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 e786ff47d73e9..45b64eb2f4ee7 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
@@ -2,6 +2,7 @@
import java.lang.reflect.Modifier;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.bson.codecs.pojo.annotations.BsonIgnore;
@@ -19,6 +20,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 PanacheMongoEntityEnhancer extends PanacheEntityEnhancer>> {
public final static String MONGO_OPERATIONS_NAME = MongoOperations.class.getName();
@@ -28,23 +30,23 @@ public class PanacheMongoEntityEnhancer extends PanacheEntityEnhancer entities = new HashMap<>();
- public PanacheMongoEntityEnhancer(IndexView index) {
- super(index, PanacheResourceProcessor.DOTNAME_PANACHE_ENTITY_BASE);
+ public PanacheMongoEntityEnhancer(IndexView index, List methodCustomizers) {
+ super(index, PanacheResourceProcessor.DOTNAME_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)));
+ indexView.getClassByName(DotName.createSimple(className)), methodCustomizers);
}
static class PanacheMongoEntityClassVisitor extends PanacheEntityClassVisitor {
public PanacheMongoEntityClassVisitor(String className, ClassVisitor outputClassVisitor,
MetamodelInfo> 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/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java
index 96ae8cdf948f6..25bfd54ddcb43 100644
--- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java
+++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java
@@ -7,6 +7,7 @@
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
import org.bson.codecs.pojo.annotations.BsonProperty;
import org.bson.types.ObjectId;
@@ -51,6 +52,8 @@
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.PanacheMethodCustomizer;
+import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem;
import io.quarkus.panache.common.deployment.PanacheRepositoryEnhancer;
public class PanacheResourceProcessor {
@@ -133,7 +136,11 @@ void buildImperative(CombinedIndexBuildItem index,
BuildProducer transformers,
BuildProducer reflectiveClass,
BuildProducer propertyMappingClass,
- BuildProducer entityClasses) {
+ BuildProducer entityClasses,
+ List methodCustomizersBuildItems) {
+
+ List methodCustomizers = methodCustomizersBuildItems.stream()
+ .map(bi -> bi.getMethodCustomizer()).collect(Collectors.toList());
PanacheMongoRepositoryEnhancer daoEnhancer = new PanacheMongoRepositoryEnhancer(index.getIndex());
Set daoClasses = new HashSet<>();
@@ -167,7 +174,7 @@ void buildImperative(CombinedIndexBuildItem index,
propertyMappingClass.produce(new PropertyMappingClassBuildStep(parameterType.name().toString()));
}
- PanacheMongoEntityEnhancer modelEnhancer = new PanacheMongoEntityEnhancer(index.getIndex());
+ PanacheMongoEntityEnhancer modelEnhancer = new PanacheMongoEntityEnhancer(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
@@ -214,7 +221,11 @@ void buildMutiny(CombinedIndexBuildItem index,
ApplicationIndexBuildItem applicationIndex,
BuildProducer reflectiveClass,
BuildProducer propertyMappingClass,
- BuildProducer transformers) {
+ 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<>();
@@ -249,7 +260,8 @@ void buildMutiny(CombinedIndexBuildItem index,
propertyMappingClass.produce(new PropertyMappingClassBuildStep(parameterType.name().toString()));
}
- ReactivePanacheMongoEntityEnhancer modelEnhancer = new ReactivePanacheMongoEntityEnhancer(index.getIndex());
+ 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
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
index f204029cdd645..679d8cb9794ce 100644
--- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java
+++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/ReactivePanacheMongoEntityEnhancer.java
@@ -2,6 +2,7 @@
import java.lang.reflect.Modifier;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import org.bson.codecs.pojo.annotations.BsonIgnore;
@@ -19,6 +20,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 ReactivePanacheMongoEntityEnhancer extends PanacheEntityEnhancer>> {
public final static String MONGO_OPERATIONS_NAME = ReactiveMongoOperations.class.getName();
@@ -28,23 +30,23 @@ public class ReactivePanacheMongoEntityEnhancer extends PanacheEntityEnhancer entities = new HashMap<>();
- public ReactivePanacheMongoEntityEnhancer(IndexView index) {
- super(index, PanacheResourceProcessor.DOTNAME_MUTINY_PANACHE_ENTITY_BASE);
+ public ReactivePanacheMongoEntityEnhancer(IndexView index, List methodCustomizers) {
+ super(index, PanacheResourceProcessor.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)));
+ indexView.getClassByName(DotName.createSimple(className)), methodCustomizers);
}
static class PanacheMongoEntityClassVisitor extends PanacheEntityClassVisitor {
public PanacheMongoEntityClassVisitor(String className, ClassVisitor outputClassVisitor,
MetamodelInfo> 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/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 5a1359ed69f36..249916ac7085a 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,204 @@ 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 947706411ef4d..7df651c465e24 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,18 @@ 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 +185,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 0000000000000..bf9a86b488bf8
--- /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 0000000000000..293d6cf78669a
--- /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 0000000000000..4fe968a6b3a53
--- /dev/null
+++ b/extensions/panache/panache-common/deployment/src/main/java/io/quarkus/panache/common/deployment/PanacheMethodCustomizerVisitor.java
@@ -0,0 +1,32 @@
+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/panache-mock/deployment/pom.xml b/extensions/panache/panache-mock/deployment/pom.xml
new file mode 100644
index 0000000000000..444e0a11672e6
--- /dev/null
+++ b/extensions/panache/panache-mock/deployment/pom.xml
@@ -0,0 +1,59 @@
+
+
+
+ quarkus-panache-mock-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../
+
+ 4.0.0
+
+ quarkus-panache-mock-deployment
+ Quarkus - Panache - Mock - Deployment
+
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+ io.quarkus
+ quarkus-panache-common-deployment
+
+
+ io.quarkus
+ quarkus-panache-mock
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ org.jboss
+ jandex
+
+
+ org.ow2.asm
+ asm
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
+
diff --git a/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockMethodCustomizer.java b/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockMethodCustomizer.java
new file mode 100644
index 0000000000000..f874cb6ece11f
--- /dev/null
+++ b/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockMethodCustomizer.java
@@ -0,0 +1,123 @@
+package io.quarkus.panache.mock.deployment;
+
+import org.jboss.jandex.MethodInfo;
+import org.jboss.jandex.Type.Kind;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import io.quarkus.panache.common.deployment.JandexUtil;
+import io.quarkus.panache.common.deployment.PanacheMethodCustomizer;
+import io.quarkus.panache.mock.PanacheMock;
+
+public class PanacheMockMethodCustomizer implements PanacheMethodCustomizer {
+
+ private final static String PANACHE_MOCK_BINARY_NAME = PanacheMock.class.getName().replace('.', '/');
+ private final static String PANACHE_MOCK_INVOKE_REAL_METHOD_EXCEPTION_BINARY_NAME = PanacheMock.InvokeRealMethodException.class
+ .getName().replace('.', '/');
+
+ @Override
+ public void customize(Type entityClassSignature, MethodInfo method, MethodVisitor mv) {
+ /*
+ * Generated code:
+ *
+ * if(PanacheMock.IsMockEnabled && PanacheMock.isMocked(TestClass.class)) {
+ * try {
+ * return (int)PanacheMock.mockMethod(TestClass.class, "foo", new Class>[] {int.class}, new Object[] {arg});
+ * } catch (PanacheMock.InvokeRealMethodException e) {
+ * // fall-through
+ * }
+ * }
+ *
+ * Bytecode approx:
+ *
+ * 0: getstatic #16 // Field PanacheMock.IsMockEnabled:Z
+ * 3: ifeq 50
+ * 6: ldc #1 // class MyTestMockito$TestClass
+ * 8: invokestatic #22 // Method PanacheMock.isMocked:(Ljava/lang/Class;)Z
+ * 11: ifeq 50
+ * 14: ldc #1 // class MyTestMockito$TestClass
+ * 16: ldc #26 // String foo
+ *
+ * 18: iconst_1
+ * 19: anewarray #27 // class java/lang/Class
+ * 22: dup
+ * 23: iconst_0
+ * 24: getstatic #29 // Field java/lang/Integer.TYPE:Ljava/lang/Class;
+ * 27: aastore
+ *
+ * 28: iconst_1
+ * 29: anewarray #3 // class java/lang/Object
+ * 32: dup
+ * 33: iconst_0
+ * 34: iload_0
+ * 35: invokestatic #35 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+ * 38: aastore
+ *
+ * 39: invokestatic #39 // Method
+ * PanacheMock.mockMethod:(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;)Ljava/lang/Object;
+ * 42: checkcast #30 // class java/lang/Integer
+ * 45: invokevirtual #43 // Method java/lang/Integer.intValue:()I
+ * 48: ireturn
+ * 49: astore_1
+ */
+ Label realMethodLabel = new Label();
+
+ mv.visitFieldInsn(Opcodes.GETSTATIC, PANACHE_MOCK_BINARY_NAME, "IsMockEnabled", "Z");
+ mv.visitJumpInsn(Opcodes.IFEQ, realMethodLabel);
+
+ mv.visitLdcInsn(entityClassSignature);
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, PANACHE_MOCK_BINARY_NAME, "isMocked", "(Ljava/lang/Class;)Z", false);
+ mv.visitJumpInsn(Opcodes.IFEQ, realMethodLabel);
+
+ Label tryStart = new Label();
+ Label tryEnd = new Label();
+ Label tryHandler = new Label();
+ mv.visitTryCatchBlock(tryStart, tryEnd, tryHandler, PANACHE_MOCK_INVOKE_REAL_METHOD_EXCEPTION_BINARY_NAME);
+ mv.visitLabel(tryStart);
+
+ mv.visitLdcInsn(entityClassSignature);
+ mv.visitLdcInsn(method.name());
+
+ mv.visitLdcInsn(method.parameters().size());
+ mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
+
+ int i = 0;
+ for (org.jboss.jandex.Type paramType : method.parameters()) {
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitLdcInsn(i);
+ JandexUtil.visitLdc(mv, paramType);
+ mv.visitInsn(Opcodes.AASTORE);
+ i++;
+ }
+
+ mv.visitLdcInsn(method.parameters().size());
+ mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
+
+ i = 0;
+ for (org.jboss.jandex.Type paramType : method.parameters()) {
+ mv.visitInsn(Opcodes.DUP);
+ mv.visitLdcInsn(i);
+ mv.visitVarInsn(JandexUtil.getLoadOpcode(paramType), i);
+ JandexUtil.boxIfRequired(mv, paramType);
+ mv.visitInsn(Opcodes.AASTORE);
+ i++;
+ }
+
+ mv.visitMethodInsn(Opcodes.INVOKESTATIC, PANACHE_MOCK_BINARY_NAME, "mockMethod",
+ "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;)Ljava/lang/Object;", false);
+ JandexUtil.unboxIfRequired(mv, method.returnType());
+ if (method.returnType().kind() != Kind.PRIMITIVE) {
+ mv.visitTypeInsn(Opcodes.CHECKCAST, method.returnType().name().toString('/'));
+ }
+
+ mv.visitInsn(JandexUtil.getReturnInstruction(method.returnType()));
+
+ mv.visitLabel(tryHandler);
+ mv.visitInsn(Opcodes.POP);
+ mv.visitLabel(tryEnd);
+
+ mv.visitLabel(realMethodLabel);
+ }
+}
diff --git a/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockResourceProcessor.java b/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockResourceProcessor.java
new file mode 100644
index 0000000000000..f70a104aef936
--- /dev/null
+++ b/extensions/panache/panache-mock/deployment/src/main/java/io/quarkus/panache/mock/deployment/PanacheMockResourceProcessor.java
@@ -0,0 +1,17 @@
+package io.quarkus.panache.mock.deployment;
+
+import io.quarkus.deployment.annotations.BuildStep;
+import io.quarkus.deployment.builditem.FeatureBuildItem;
+import io.quarkus.panache.common.deployment.PanacheMethodCustomizerBuildItem;
+
+public final class PanacheMockResourceProcessor {
+ @BuildStep
+ FeatureBuildItem featureBuildItem() {
+ return new FeatureBuildItem(FeatureBuildItem.PANACHE_MOCK);
+ }
+
+ @BuildStep
+ public PanacheMethodCustomizerBuildItem addMocks() {
+ return new PanacheMethodCustomizerBuildItem(new PanacheMockMethodCustomizer());
+ }
+}
diff --git a/extensions/panache/panache-mock/pom.xml b/extensions/panache/panache-mock/pom.xml
new file mode 100644
index 0000000000000..87c2f12931591
--- /dev/null
+++ b/extensions/panache/panache-mock/pom.xml
@@ -0,0 +1,22 @@
+
+
+
+ quarkus-build-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../../../build-parent/pom.xml
+
+ 4.0.0
+
+ quarkus-panache-mock-parent
+ Quarkus - Panache - Mock
+ pom
+
+ runtime
+ deployment
+
+
+
+
diff --git a/extensions/panache/panache-mock/runtime/pom.xml b/extensions/panache/panache-mock/runtime/pom.xml
new file mode 100644
index 0000000000000..28422888d0182
--- /dev/null
+++ b/extensions/panache/panache-mock/runtime/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+ quarkus-panache-mock-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../
+
+ 4.0.0
+
+ quarkus-panache-mock
+ Quarkus - Panache - Mock - Runtime
+ An opinionated approach to make Hibernate as easy as possible
+
+
+ io.quarkus
+ quarkus-core
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-junit5-mockito
+
+
+ org.mockito
+ mockito-core
+
+
+
+
+
+
+ io.quarkus
+ quarkus-bootstrap-maven-plugin
+
+
+
+
diff --git a/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/PanacheMock.java b/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/PanacheMock.java
new file mode 100644
index 0000000000000..306983ccd8196
--- /dev/null
+++ b/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/PanacheMock.java
@@ -0,0 +1,93 @@
+package io.quarkus.panache.mock;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.mockito.Mockito;
+import org.mockito.internal.invocation.DefaultInvocationFactory;
+import org.mockito.internal.invocation.InterceptedInvocation;
+import org.mockito.internal.invocation.RealMethod;
+import org.mockito.internal.util.MockUtil;
+import org.mockito.invocation.MockHandler;
+import org.mockito.mock.MockCreationSettings;
+import org.mockito.verification.VerificationMode;
+
+public class PanacheMock {
+
+ public static boolean IsMockEnabled = false;
+
+ private final static Map, Object> mocks = new HashMap<>();
+
+ @SuppressWarnings("unchecked")
+ public static T getMock(Class klass) {
+ return (T) mocks.get(klass);
+ }
+
+ public static void mock(Class>... classes) {
+ for (Class> klass : classes) {
+ mocks.computeIfAbsent(klass, v -> Mockito.mock(klass));
+ }
+ IsMockEnabled = !mocks.isEmpty();
+ }
+
+ public static void reset() {
+ mocks.clear();
+ IsMockEnabled = false;
+ }
+
+ public static boolean isMocked(Class> klass) {
+ return mocks.containsKey(klass);
+ }
+
+ public static Object mockMethod(Class> klass, String methodName, Class>[] parameterTypes, Object[] args)
+ throws InvokeRealMethodException {
+ try {
+ Method invokedMethod = klass.getDeclaredMethod(methodName, parameterTypes);
+ Object mock = getMock(klass);
+ MockCreationSettings> settings = MockUtil.getMockSettings(mock);
+ MyRealMethod myRealMethod = new MyRealMethod();
+ InterceptedInvocation invocation = DefaultInvocationFactory.createInvocation(mock, invokedMethod, args,
+ myRealMethod, settings, null);
+ MockHandler> handler = MockUtil.getMockHandler(mock);
+ return handler.handle(invocation);
+ } catch (InvokeRealMethodException e) {
+ throw e;
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ //
+ // Delegating
+
+ public static T verify(Class klass) {
+ return Mockito.verify(getMock(klass));
+ }
+
+ public static T verify(Class klass, VerificationMode verificationMode) {
+ return Mockito.verify(getMock(klass), verificationMode);
+ }
+
+ @SuppressWarnings("serial")
+ public static class InvokeRealMethodException extends Exception {
+ }
+
+ @SuppressWarnings("serial")
+ public static class MyRealMethod implements RealMethod {
+
+ @Override
+ public boolean isInvokable() {
+ return true;
+ }
+
+ @Override
+ public Object invoke() throws Throwable {
+ throw new InvokeRealMethodException();
+ }
+
+ }
+
+}
diff --git a/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/impl/PanacheMockAfterEachTest.java b/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/impl/PanacheMockAfterEachTest.java
new file mode 100644
index 0000000000000..53a3b60cf959f
--- /dev/null
+++ b/extensions/panache/panache-mock/runtime/src/main/java/io/quarkus/panache/mock/impl/PanacheMockAfterEachTest.java
@@ -0,0 +1,13 @@
+package io.quarkus.panache.mock.impl;
+
+import io.quarkus.panache.mock.PanacheMock;
+import io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback;
+
+public class PanacheMockAfterEachTest implements QuarkusTestAfterEachCallback {
+
+ @Override
+ public void afterEach(Object testInstance) {
+ PanacheMock.reset();
+ }
+
+}
diff --git a/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..50f6f3d5edd65
--- /dev/null
+++ b/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,15 @@
+---
+name: "Panache Mocking"
+metadata:
+ keywords:
+ - "hibernate-orm-panache"
+ - "mongodb-panache"
+ - "panache"
+ - "hibernate"
+ - "jpa"
+ - "test"
+ - "mock"
+ guide: "https://quarkus.io/guides/hibernate-orm-panache"
+ categories:
+ - "data"
+ status: "stable"
diff --git a/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback b/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback
new file mode 100644
index 0000000000000..fc04ccca8c2d8
--- /dev/null
+++ b/extensions/panache/panache-mock/runtime/src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback
@@ -0,0 +1 @@
+io.quarkus.panache.mock.impl.PanacheMockAfterEachTest
diff --git a/extensions/panache/pom.xml b/extensions/panache/pom.xml
index 34220535ae042..8a36507c66f7f 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 9006bfdc8c368..183f3f7c413ad 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 add79fb5bd40f..e483c4f56aa99 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.Assertions;
+import org.junit.jupiter.api.Order;
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,45 @@ public void testBug7721() {
public void testBug8254() {
RestAssured.when().get("/test/8254").then().body(is("OK"));
}
+
+ @DisabledOnNativeImage
+ @Test
+ @Order(1)
+ 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());
+
+ PanacheMock.verify(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());
+ }
+
+ @DisabledOnNativeImage
+ @Test
+ @Order(2)
+ public void testPanacheMockingWasCleared() {
+ Assertions.assertFalse(PanacheMock.IsMockEnabled);
+ }
}