diff --git a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java index 47d6990c9d4fb..0eecfbab57110 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/ExtensionLoader.java @@ -491,6 +491,7 @@ private static Consumer loadStepsFromClass(Class clazz, final Parameter[] methodParameters = method.getParameters(); final Record recordAnnotation = method.getAnnotation(Record.class); final boolean isRecorder = recordAnnotation != null; + final boolean identityComparison = isRecorder ? recordAnnotation.useIdentityComparisonForParameters() : true; if (isRecorder) { boolean recorderFound = false; for (Class p : method.getParameterTypes()) { @@ -810,7 +811,7 @@ public void execute(final BuildContext bc) { BytecodeRecorderImpl bri = isRecorder ? new BytecodeRecorderImpl(recordAnnotation.value() == ExecutionTime.STATIC_INIT, clazz.getSimpleName(), method.getName(), - Integer.toString(method.toString().hashCode())) + Integer.toString(method.toString().hashCode()), identityComparison) : null; for (int i = 0; i < methodArgs.length; i++) { methodArgs[i] = methodParamFns.get(i).apply(bc, bri); diff --git a/core/deployment/src/main/java/io/quarkus/deployment/annotations/Record.java b/core/deployment/src/main/java/io/quarkus/deployment/annotations/Record.java index 431f089b0907a..dde3e43d15dbd 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/annotations/Record.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/annotations/Record.java @@ -53,4 +53,15 @@ */ boolean optional() default false; + /** + * If this is set to false then parameters are considered equal based on equals/hashCode, instead of identity. + * + * This is an advanced option, it is only useful if you are recording lots of objects that you expect to be the same + * but have different identities. This allows multiple objects at deployment time to be interned into a single + * object at runtime. + * + * This is an advanced option, most recorders don't want this. + */ + boolean useIdentityComparisonForParameters() default true; + } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/builditem/RecordableConstructorBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RecordableConstructorBuildItem.java new file mode 100644 index 0000000000000..55e27cc1c3f03 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/builditem/RecordableConstructorBuildItem.java @@ -0,0 +1,22 @@ +package io.quarkus.deployment.builditem; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Indicates that the given class should be instantiated with the constructor with the most parameters + * when the object is bytecode recorded. + * + * An alternative to {@link RecordableConstructorBuildItem} for when the objects cannot be annotated + */ +public final class RecordableConstructorBuildItem extends MultiBuildItem { + + private final Class clazz; + + public RecordableConstructorBuildItem(Class clazz) { + this.clazz = clazz; + } + + public Class getClazz() { + return clazz; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java index da5b9655528a0..72a43e8af0fe4 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/recording/BytecodeRecorderImpl.java @@ -10,6 +10,7 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; @@ -122,6 +123,8 @@ public class BytecodeRecorderImpl implements RecorderContext { private final Function methodCreatorFunction; private final List loaders = new ArrayList<>(); + private final Set classesToUseRecorableConstructor = new HashSet<>(); + private final boolean useIdentityComparison; /** * the maximum number of instruction groups that can be added to a method. This is to limit the size of the method @@ -135,7 +138,8 @@ public class BytecodeRecorderImpl implements RecorderContext { private int deferredParameterCount = 0; private boolean loadComplete; - public BytecodeRecorderImpl(boolean staticInit, String buildStepName, String methodName, String uniqueHash) { + public BytecodeRecorderImpl(boolean staticInit, String buildStepName, String methodName, String uniqueHash, + boolean useIdentityComparison) { this( Thread.currentThread().getContextClassLoader(), staticInit, @@ -145,7 +149,7 @@ public BytecodeRecorderImpl(boolean staticInit, String buildStepName, String met }, classCreator -> { return startupMethodCreator(buildStepName, methodName, classCreator); - }); + }, useIdentityComparison); } private static MethodCreator startupMethodCreator(String buildStepName, String methodName, ClassCreator classCreator) { @@ -176,17 +180,18 @@ private static String toClassName(String buildStepName, String methodName, Strin }, classCreator -> { return startupMethodCreator(null, null, classCreator); - }); + }, true); } public BytecodeRecorderImpl(ClassLoader classLoader, boolean staticInit, String className, Function classCreatorFunction, - Function methodCreatorFunction) { + Function methodCreatorFunction, boolean useIdentityComparison) { this.classLoader = classLoader; this.staticInit = staticInit; this.className = className; this.classCreatorFunction = classCreatorFunction; this.methodCreatorFunction = methodCreatorFunction; + this.useIdentityComparison = useIdentityComparison; } public boolean isEmpty() { @@ -355,6 +360,10 @@ public void run() { } } + public void markClassAsConstructorRecordable(Class clazz) { + classesToUseRecorableConstructor.add(clazz); + } + private ProxyInstance getProxyInstance(Class returnType) throws InstantiationException, IllegalAccessException { boolean returnInterface = returnType.isInterface(); ProxyFactory proxyFactory = returnValueProxy.get(returnType); @@ -387,6 +396,17 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl && method.getReturnType().equals(String.class)) { return "Runtime proxy of " + returnType + " with id " + key; } + if (method.getName().equals("hashCode") + && method.getParameterTypes().length == 0 + && method.getReturnType().equals(int.class)) { + return System.identityHashCode(proxy); + } + if (method.getName().equals("equals") + && method.getParameterTypes().length == 1 + && method.getParameterTypes()[0] == Object.class + && method.getReturnType().equals(boolean.class)) { + return proxy == args[0]; + } throw new RuntimeException( "You cannot invoke " + method.getName() + "() directly on an object returned from the bytecode recorder, you can only pass it back into the recorder as a parameter"); @@ -413,7 +433,7 @@ public void writeBytecode(ClassOutput classOutput) { //now create instances of all the classes we invoke on and store them in variables as well Map classInstanceVariables = new HashMap<>(); - Map parameterMap = new IdentityHashMap<>(); + Map parameterMap = useIdentityComparison ? new IdentityHashMap<>() : new HashMap<>(); //THIS IS FAIRLY COMPLEX //the simple approach of just writing out the serialized invocations and method parameters as they are needed @@ -441,7 +461,7 @@ public void writeBytecode(ClassOutput classOutput) { if (!classInstanceVariables.containsKey(call.theClass)) { //this is a new recorder, create a deferred value that will allocate an array position for //the recorder - DeferredArrayStoreParameter value = new DeferredArrayStoreParameter() { + DeferredArrayStoreParameter value = new DeferredArrayStoreParameter(null, call.theClass) { @Override ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) { return method.newInstance(ofConstructor(call.theClass)); @@ -599,7 +619,7 @@ ResultHandle doLoad(MethodContext creator, MethodCreator method, ResultHandle ar Object res = substitution.serialize(param); DeferredParameter serialized = loadObjectInstance(res, existing, holder.to, relaxedValidation); SubstitutionHolder finalHolder = holder; - return new DeferredArrayStoreParameter() { + return new DeferredArrayStoreParameter(param, expectedType) { @Override void doPrepare(MethodContext context) { @@ -624,7 +644,7 @@ ResultHandle createValue(MethodContext creator, MethodCreator method, ResultHand Optional val = (Optional) param; if (val.isPresent()) { DeferredParameter res = loadObjectInstance(val.get(), existing, Object.class, relaxedValidation); - return new DeferredArrayStoreParameter() { + return new DeferredArrayStoreParameter(param, expectedType) { @Override void doPrepare(MethodContext context) { @@ -642,7 +662,7 @@ ResultHandle createValue(MethodContext context, MethodCreator method, ResultHand } }; } else { - return new DeferredArrayStoreParameter() { + return new DeferredArrayStoreParameter(param, expectedType) { @Override ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) { return method.invokeStaticMethod(ofMethod(Optional.class, "empty", Optional.class)); @@ -659,22 +679,6 @@ ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle ar return method.load((String) param); } }; - } else if (param instanceof Integer) { - return new DeferredParameter() { - @Override - ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { - return method.invokeStaticMethod(ofMethod(Integer.class, "valueOf", Integer.class, int.class), - method.load((Integer) param)); - } - }; - } else if (param instanceof Boolean) { - return new DeferredParameter() { - @Override - ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { - return method.invokeStaticMethod(ofMethod(Boolean.class, "valueOf", Boolean.class, boolean.class), - method.load((Boolean) param)); - } - }; } else if (param instanceof URL) { String url = ((URL) param).toExternalForm(); return new DeferredParameter() { @@ -757,126 +761,62 @@ ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle ar } }; } - } else if (expectedType == boolean.class) { + } else if (expectedType == boolean.class || expectedType == Boolean.class || param instanceof Boolean) { return new DeferredParameter() { @Override ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { return method.load((boolean) param); } }; - } else if (expectedType == Boolean.class) { - return new DeferredParameter() { - @Override - ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { - return method.invokeStaticMethod(ofMethod(Boolean.class, "valueOf", Boolean.class, boolean.class), - method.load((boolean) param)); - } - }; - } else if (expectedType == int.class) { + } else if (expectedType == int.class || expectedType == Integer.class || param instanceof Integer) { return new DeferredParameter() { @Override ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { return method.load((int) param); } }; - } else if (expectedType == Integer.class) { - return new DeferredParameter() { - @Override - ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { - return method.invokeStaticMethod(ofMethod(Integer.class, "valueOf", Integer.class, int.class), - method.load((int) param)); - } - }; - } else if (expectedType == short.class) { + } else if (expectedType == short.class || expectedType == Short.class || param instanceof Short) { return new DeferredParameter() { @Override ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { return method.load((short) param); } }; - } else if (expectedType == Short.class || param instanceof Short) { - return new DeferredParameter() { - @Override - ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { - return method.invokeStaticMethod(ofMethod(Short.class, "valueOf", Short.class, short.class), - method.load((short) param)); - } - }; - } else if (expectedType == byte.class) { + } else if (expectedType == byte.class || expectedType == Byte.class || param instanceof Byte) { return new DeferredParameter() { @Override ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { return method.load((byte) param); } }; - } else if (expectedType == Byte.class || param instanceof Byte) { - return new DeferredParameter() { - @Override - ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { - return method.invokeStaticMethod(ofMethod(Byte.class, "valueOf", Byte.class, byte.class), - method.load((byte) param)); - } - }; - } else if (expectedType == char.class) { + } else if (expectedType == char.class || expectedType == Character.class || param instanceof Character) { return new DeferredParameter() { @Override ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { return method.load((char) param); } }; - } else if (expectedType == Character.class || param instanceof Character) { - return new DeferredParameter() { - @Override - ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { - return method.invokeStaticMethod(ofMethod(Character.class, "valueOf", Character.class, char.class), - method.load((char) param)); - } - }; - } else if (expectedType == long.class) { + } else if (expectedType == long.class || expectedType == Long.class || param instanceof Long) { return new DeferredParameter() { @Override ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { return method.load((long) param); } }; - } else if (expectedType == Long.class || param instanceof Long) { - return new DeferredParameter() { - @Override - ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { - return method.invokeStaticMethod(ofMethod(Long.class, "valueOf", Long.class, long.class), - method.load((long) param)); - } - }; - } else if (expectedType == float.class) { + } else if (expectedType == float.class || expectedType == Float.class || param instanceof Float) { return new DeferredParameter() { @Override ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { return method.load((float) param); } }; - } else if (expectedType == Float.class || param instanceof Float) { - return new DeferredParameter() { - @Override - ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { - return method.invokeStaticMethod(ofMethod(Float.class, "valueOf", Float.class, float.class), - method.load((float) param)); - } - }; - } else if (expectedType == double.class) { + } else if (expectedType == double.class || expectedType == Double.class || param instanceof Double) { return new DeferredParameter() { @Override ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { return method.load((double) param); } }; - } else if (expectedType == Double.class || param instanceof Double) { - return new DeferredParameter() { - @Override - ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { - return method.invokeStaticMethod(ofMethod(Double.class, "valueOf", Double.class, double.class), - method.load((double) param)); - } - }; } else if (expectedType.isArray()) { int length = Array.getLength(param); DeferredParameter[] components = new DeferredParameter[length]; @@ -886,7 +826,7 @@ ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle ar expectedType.getComponentType(), relaxedValidation); components[i] = component; } - return new DeferredArrayStoreParameter() { + return new DeferredArrayStoreParameter(param, expectedType) { @Override void doPrepare(MethodContext context) { @@ -944,7 +884,7 @@ ResultHandle createValue(MethodContext context, MethodCreator method, ResultHand constructorParamsHandles[iterator.previousIndex()] = retValue; } } - return new DeferredArrayStoreParameter() { + return new DeferredArrayStoreParameter(annotationProxy.getAnnotationLiteralType()) { @Override ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) { MethodDescriptor constructor = MethodDescriptor.ofConstructor(annotationProxy.getAnnotationLiteralType(), @@ -1184,6 +1124,26 @@ public void prepare(MethodContext context) { nonDefaultConstructorHandles[i] = loadObjectInstance(obj, existing, nonDefaultConstructorHolder.constructor.getParameterTypes()[count++], relaxedValidation); } + } else if (classesToUseRecorableConstructor.contains(param.getClass())) { + Constructor current = null; + int count = 0; + for (var c : param.getClass().getConstructors()) { + if (current == null || current.getParameterTypes().length < c.getParameterTypes().length) { + current = c; + count = 0; + } else if (current != null && current.getParameterTypes().length == c.getParameterTypes().length) { + count++; + } + } + if (current == null || count > 0) { + throw new RuntimeException("Unable to determine the recordable constructor to use for " + param.getClass()); + } + nonDefaultConstructorHolder = new NonDefaultConstructorHolder(current, null); + nonDefaultConstructorHandles = new DeferredParameter[current.getParameterCount()]; + for (int i = 0; i < current.getParameterCount(); ++i) { + String name = current.getParameters()[i].getName(); + constructorParamNameMap.put(name, i); + } } else { for (Constructor ctor : param.getClass().getConstructors()) { if (ctor.isAnnotationPresent(RecordableConstructor.class)) { @@ -1229,7 +1189,6 @@ public void prepare(MethodContext context) { for (Object c : propertyValue) { DeferredParameter toAdd = loadObjectInstance(c, existing, Object.class, relaxedValidation); params.add(toAdd); - } setupSteps.add(new SerializationStep() { @Override @@ -1370,12 +1329,14 @@ public void prepare(MethodContext context) { setupSteps.add(new SerializationStep() { @Override public void handle(MethodContext context, MethodCreator method, DeferredArrayStoreParameter out) { + ResultHandle object = context.loadDeferred(out); + ResultHandle resultVal = context.loadDeferred(val); method.invokeVirtualMethod( ofMethod(param.getClass(), i.getWriteMethod().getName(), i.getWriteMethod().getReturnType(), finalPropertyType), - context.loadDeferred(out), - context.loadDeferred(val)); + object, + resultVal); } @Override @@ -1450,7 +1411,7 @@ public void prepare(MethodContext context) { //create a deferred value to represet the object itself. This allows the creation to be split //over multiple methods, which is important if this is a large object - DeferredArrayStoreParameter objectValue = new DeferredArrayStoreParameter() { + DeferredArrayStoreParameter objectValue = new DeferredArrayStoreParameter(param, expectedType) { @Override ResultHandle createValue(MethodContext context, MethodCreator method, ResultHandle array) { ResultHandle out; @@ -1462,7 +1423,7 @@ ResultHandle createValue(MethodContext context, MethodCreator method, ResultHand Arrays.stream(finalCtorHandles).map(m -> context.loadDeferred(m)) .toArray(ResultHandle[]::new)); } else { - if (List.class.isAssignableFrom(param.getClass())) { + if (List.class.isAssignableFrom(param.getClass()) && expectedType == List.class) { // list is a common special case, so let's handle it List listParam = (List) param; if (listParam.isEmpty()) { @@ -1498,7 +1459,7 @@ ResultHandle createValue(MethodContext context, MethodCreator method, ResultHand }; //now return the actual deferred parameter that represents the result of construction - return new DeferredArrayStoreParameter() { + return new DeferredArrayStoreParameter(param, expectedType) { @Override void doPrepare(MethodContext context) { @@ -1535,7 +1496,7 @@ ResultHandle createValue(MethodContext context, MethodCreator method, ResultHand private DeferredParameter findLoaded(final Object param) { for (ObjectLoader loader : loaders) { if (loader.canHandleObject(param, staticInit)) { - return new DeferredArrayStoreParameter() { + return new DeferredArrayStoreParameter(param, param.getClass()) { @Override ResultHandle createValue(MethodContext creator, MethodCreator method, ResultHandle array) { return loader.load(method, param, staticInit); @@ -1785,6 +1746,25 @@ void doPrepare(MethodContext context) { abstract class DeferredArrayStoreParameter extends DeferredParameter { final int arrayIndex; + final String returnType; + + DeferredArrayStoreParameter(String expectedType) { + returnType = expectedType; + arrayIndex = deferredParameterCount++; + } + + DeferredArrayStoreParameter(Object target, Class expectedType) { + if (expectedType == List.class) { + returnType = expectedType.getName(); + } else if (target != null && !(target instanceof Proxy) && isAccessible(target.getClass())) { + returnType = target.getClass().getName(); + } else if (expectedType != null && isAccessible(expectedType)) { + returnType = expectedType.getName(); + } else { + returnType = null; + } + arrayIndex = deferredParameterCount++; + } /** * method that contains the logic to actually create the stored value @@ -1807,13 +1787,23 @@ public void write(MethodContext context, MethodCreator method, ResultHandle arra @Override final ResultHandle doLoad(MethodContext context, MethodCreator method, ResultHandle array) { - return method.readArrayValue(array, arrayIndex); + ResultHandle resultHandle = method.readArrayValue(array, arrayIndex); + if (returnType == null) { + return resultHandle; + } + return method.checkCast(resultHandle, returnType); } - DeferredArrayStoreParameter() { - arrayIndex = deferredParameterCount++; - } + } + private boolean isAccessible(Class expectedType) { + if (!Modifier.isPublic(expectedType.getModifiers())) { + return false; + } + if (expectedType.getPackage() == null) { + return true; + } + return expectedType.getModule().isExported(expectedType.getPackage().getName()); } /** diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java index 12a767732f634..ea615a25e2c81 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/MainClassBuildStep.java @@ -47,6 +47,7 @@ import io.quarkus.deployment.builditem.MainClassBuildItem; import io.quarkus.deployment.builditem.ObjectSubstitutionBuildItem; import io.quarkus.deployment.builditem.QuarkusApplicationClassBuildItem; +import io.quarkus.deployment.builditem.RecordableConstructorBuildItem; import io.quarkus.deployment.builditem.StaticBytecodeRecorderBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; @@ -115,6 +116,7 @@ void build(List staticInitTasks, List features, BuildProducer appClassNameProducer, List loaders, + List recordableConstructorBuildItems, BuildProducer generatedClass, LaunchModeBuildItem launchMode, LiveReloadBuildItem liveReloadBuildItem, @@ -174,7 +176,8 @@ void build(List staticInitTasks, TryBlock tryBlock = mv.tryBlock(); tryBlock.invokeStaticMethod(CONFIGURE_STEP_TIME_START); for (StaticBytecodeRecorderBuildItem holder : staticInitTasks) { - writeRecordedBytecode(holder.getBytecodeRecorder(), null, substitutions, loaders, gizmoOutput, startupContext, + writeRecordedBytecode(holder.getBytecodeRecorder(), null, substitutions, recordableConstructorBuildItems, loaders, + gizmoOutput, startupContext, tryBlock); } tryBlock.returnValue(null); @@ -269,6 +272,7 @@ void build(List staticInitTasks, tryBlock.invokeStaticMethod(CONFIGURE_STEP_TIME_START); for (MainBytecodeRecorderBuildItem holder : mainMethod) { writeRecordedBytecode(holder.getBytecodeRecorder(), holder.getGeneratedStartupContextClassName(), substitutions, + recordableConstructorBuildItems, loaders, gizmoOutput, startupContext, tryBlock); } @@ -437,6 +441,7 @@ private void generateMainForQuarkusApplication(String quarkusApplicationClassNam private void writeRecordedBytecode(BytecodeRecorderImpl recorder, String fallbackGeneratedStartupTaskClassName, List substitutions, + List recordableConstructorBuildItems, List loaders, GeneratedClassGizmoAdaptor gizmoOutput, ResultHandle startupContext, BytecodeCreator bytecodeCreator) { @@ -452,6 +457,9 @@ private void writeRecordedBytecode(BytecodeRecorderImpl recorder, String fallbac for (BytecodeRecorderObjectLoaderBuildItem item : loaders) { recorder.registerObjectLoader(item.getObjectLoader()); } + for (var item : recordableConstructorBuildItems) { + recorder.markClassAsConstructorRecordable(item.getClazz()); + } recorder.writeBytecode(gizmoOutput); } diff --git a/core/deployment/src/test/java/io/quarkus/deployment/recording/BytecodeRecorderTestCase.java b/core/deployment/src/test/java/io/quarkus/deployment/recording/BytecodeRecorderTestCase.java index bc06f4a95f553..6fa3cbc65e3d0 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/recording/BytecodeRecorderTestCase.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/recording/BytecodeRecorderTestCase.java @@ -280,7 +280,7 @@ public void testNewInstance() throws Exception { recorder.add(instance); recorder.add(instance); recorder.result(instance); - }, new TestJavaBean(null, 2)); + }, new TestJavaBean(null, 2, 2)); } @Test diff --git a/core/deployment/src/test/java/io/quarkus/deployment/recording/TestJavaBean.java b/core/deployment/src/test/java/io/quarkus/deployment/recording/TestJavaBean.java index d60e6c12192d2..772b76f8d4967 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/recording/TestJavaBean.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/recording/TestJavaBean.java @@ -8,6 +8,12 @@ public class TestJavaBean { public TestJavaBean() { } + public TestJavaBean(String sval, int ival, Integer boxedIval) { + this.sval = sval; + this.ival = ival; + this.boxedIval = boxedIval; + } + public TestJavaBean(String sval, int ival) { this.sval = sval; this.ival = ival; @@ -21,6 +27,7 @@ public TestJavaBean(String sval, int ival, Supplier supplier) { private String sval; private int ival; + private Integer boxedIval = 0; private Supplier supplier; public String getSval() { @@ -40,6 +47,15 @@ public void setIval(int ival) { this.ival = ival; } + public Integer getBoxedIval() { + return boxedIval; + } + + public TestJavaBean setBoxedIval(Integer boxedIval) { + this.boxedIval = boxedIval; + return this; + } + @Override public boolean equals(Object o) { if (this == o) @@ -48,7 +64,7 @@ public boolean equals(Object o) { return false; TestJavaBean that = (TestJavaBean) o; boolean matchesSimple = ival == that.ival && - Objects.equals(sval, that.sval); + Objects.equals(sval, that.sval) && Objects.equals(boxedIval, that.boxedIval); if (!matchesSimple) { return false; } @@ -81,6 +97,7 @@ public String toString() { return "TestJavaBean{" + "sval='" + sval + '\'' + ", ival=" + ival + + ", boxedIval=" + boxedIval + '}'; } } diff --git a/core/deployment/src/test/java/io/quarkus/deployment/recording/TestRecorder.java b/core/deployment/src/test/java/io/quarkus/deployment/recording/TestRecorder.java index b4de367830b74..41eb65ac76c45 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/recording/TestRecorder.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/recording/TestRecorder.java @@ -69,6 +69,7 @@ public void bean(NonSerializable bean) { public void add(RuntimeValue bean) { bean.getValue().setIval(bean.getValue().getIval() + 1); + bean.getValue().setBoxedIval(bean.getValue().getBoxedIval() + 1); } public void bean(TestConstructorBean bean) { diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/UnusedExclusionTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/UnusedExclusionTest.java index ce03e4d9ede99..b78865359e095 100644 --- a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/UnusedExclusionTest.java +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/unused/UnusedExclusionTest.java @@ -53,7 +53,7 @@ public void execute(BuildContext context) { BeanContainer beanContainer = context.consume(BeanContainerBuildItem.class).getValue(); BytecodeRecorderImpl bytecodeRecorder = new BytecodeRecorderImpl(true, TestRecorder.class.getSimpleName(), - "test", "" + TestRecorder.class.hashCode()); + "test", "" + TestRecorder.class.hashCode(), true); // We need to use reflection due to some class loading problems Object recorderProxy = bytecodeRecorder.getRecordingProxy(TestRecorder.class); try { diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 713387aad45de..bf5a3fd659b84 100644 --- a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -62,6 +62,8 @@ import org.jboss.resteasy.reactive.server.model.Features; import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer; import org.jboss.resteasy.reactive.server.model.ParamConverterProviders; +import org.jboss.resteasy.reactive.server.model.ServerMethodParameter; +import org.jboss.resteasy.reactive.server.model.ServerResourceMethod; import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner; import org.jboss.resteasy.reactive.spi.BeanFactory; @@ -86,6 +88,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; import io.quarkus.deployment.builditem.LaunchModeBuildItem; +import io.quarkus.deployment.builditem.RecordableConstructorBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; @@ -126,6 +129,7 @@ import io.quarkus.resteasy.reactive.spi.MessageBodyReaderOverrideBuildItem; import io.quarkus.resteasy.reactive.spi.MessageBodyWriterBuildItem; import io.quarkus.resteasy.reactive.spi.MessageBodyWriterOverrideBuildItem; +import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.RuntimeValue; import io.quarkus.security.AuthenticationCompletionException; import io.quarkus.security.AuthenticationFailedException; @@ -170,6 +174,12 @@ MinNettyAllocatorMaxOrderBuildItem setMinimalNettyMaxOrderSize() { return new MinNettyAllocatorMaxOrderBuildItem(3); } + @BuildStep + void recordableConstructor(BuildProducer ctors) { + ctors.produce(new RecordableConstructorBuildItem(ServerResourceMethod.class)); + ctors.produce(new RecordableConstructorBuildItem(ServerMethodParameter.class)); + } + @BuildStep void vertxIntegration(BuildProducer writerBuildItemBuildProducer) { writerBuildItemBuildProducer.produce(new MessageBodyWriterBuildItem(ServerVertxBufferMessageBodyWriter.class.getName(), @@ -258,8 +268,12 @@ public void unremoveableBeans(Optional resource @SuppressWarnings("unchecked") @BuildStep - @Record(ExecutionTime.STATIC_INIT) - public void setupEndpoints(Capabilities capabilities, BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, + //note useIdentityComparisonForParameters=false + //resteasy can generate lots of small collections with similar values as part of its metadata gathering + //this allows multiple objects to be compressed into a single object at runtime + //saving memory and reducing reload time + @Record(value = ExecutionTime.STATIC_INIT, useIdentityComparisonForParameters = false) + public void setupEndpoints(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem, BeanContainerBuildItem beanContainerBuildItem, ResteasyReactiveConfig config, Optional resourceScanningResultBuildItem, @@ -579,6 +593,7 @@ private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName an .setGlobalHandlerCustomers( new ArrayList<>(Collections.singletonList(new SecurityContextOverrideHandler.Customizer()))) //TODO: should be pluggable .setResourceClasses(resourceClasses) + .setDevelopmentMode(launchModeBuildItem.getLaunchMode() == LaunchMode.DEVELOPMENT) .setLocatableResourceClasses(subResourceClasses) .setParamConverterProviders(paramConverterProviders); quarkusRestDeploymentInfoBuildItemBuildProducer diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java index b440fb33b2d6f..2d17f503afc70 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/MethodParameter.java @@ -1,5 +1,7 @@ package org.jboss.resteasy.reactive.common.model; +import java.util.Objects; + public class MethodParameter { public String name; public String type; @@ -117,4 +119,25 @@ public String toString() { ", type='" + type + '\'' + '}'; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + MethodParameter that = (MethodParameter) o; + return encoded == that.encoded && single == that.single && optional == that.optional + && isObtainedAsCollection == that.isObtainedAsCollection && Objects.equals(name, that.name) + && Objects.equals(type, that.type) && Objects.equals(declaredType, that.declaredType) + && Objects.equals(declaredUnresolvedType, that.declaredUnresolvedType) + && Objects.equals(signature, that.signature) && parameterType == that.parameterType + && Objects.equals(defaultValue, that.defaultValue); + } + + @Override + public int hashCode() { + return Objects.hash(name, type, declaredType, declaredUnresolvedType, signature, parameterType, encoded, single, + defaultValue, optional, isObtainedAsCollection); + } } diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceMethod.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceMethod.java index 2a05afd436575..388cd674a01c1 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceMethod.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/model/ResourceMethod.java @@ -65,6 +65,31 @@ public class ResourceMethod { private boolean isMultipart; private List subResourceMethods; + public ResourceMethod() { + } + + public ResourceMethod(String httpMethod, String path, String[] produces, String sseElementType, String[] consumes, + Set nameBindingNames, String name, String returnType, String simpleReturnType, MethodParameter[] parameters, + boolean blocking, boolean suspended, boolean isSse, boolean isFormParamRequired, boolean isMultipart, + List subResourceMethods) { + this.httpMethod = httpMethod; + this.path = path; + this.produces = produces; + this.sseElementType = sseElementType; + this.consumes = consumes; + this.nameBindingNames = nameBindingNames; + this.name = name; + this.returnType = returnType; + this.simpleReturnType = simpleReturnType; + this.parameters = parameters; + this.blocking = blocking; + this.suspended = suspended; + this.isSse = isSse; + this.isFormParamRequired = isFormParamRequired; + this.isMultipart = isMultipart; + this.subResourceMethods = subResourceMethods; + } + public boolean isResourceLocator() { return httpMethod == null; } diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 47297bb36ace2..faa86d3ef68a2 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -281,6 +281,12 @@ + + maven-compiler-plugin + + true + + maven-javadoc-plugin diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java index cba84c1d8852a..cb504d855a516 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/DeploymentInfo.java @@ -32,6 +32,7 @@ public class DeploymentInfo { private Function clientProxyUnwrapper; private String applicationPath; private List globalHandlerCustomers = new ArrayList<>(); + private boolean developmentMode; public ResourceInterceptors getInterceptors() { return interceptors; @@ -167,4 +168,13 @@ public DeploymentInfo setGlobalHandlerCustomers(List glo this.globalHandlerCustomers = globalHandlerCustomers; return this; } + + public boolean isDevelopmentMode() { + return developmentMode; + } + + public DeploymentInfo setDevelopmentMode(boolean developmentMode) { + this.developmentMode = developmentMode; + return this; + } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/GeneratedParameterConverter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/GeneratedParameterConverter.java index c88eb3850bcb9..9bded4a0ff5aa 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/GeneratedParameterConverter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/parameters/converters/GeneratedParameterConverter.java @@ -1,5 +1,7 @@ package org.jboss.resteasy.reactive.server.core.parameters.converters; +import java.util.Objects; + public class GeneratedParameterConverter implements ParameterConverterSupplier { private String className; @@ -24,4 +26,19 @@ public GeneratedParameterConverter setClassName(String className) { this.className = className; return this; } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + GeneratedParameterConverter that = (GeneratedParameterConverter) o; + return Objects.equals(className, that.className); + } + + @Override + public int hashCode() { + return Objects.hash(className); + } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java index 44775f7230f60..cb953a67c9efa 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/core/startup/RuntimeResourceDeployment.java @@ -106,6 +106,8 @@ public class RuntimeResourceDeployment { * If the runtime will always default to blocking (e.g. Servlet) */ private final boolean defaultBlocking; + private final BlockingHandler blockingHandler; + private final ResponseWriterHandler responseWriterHandler; public RuntimeResourceDeployment(DeploymentInfo info, Supplier executorSupplier, CustomServerRestHandlers customServerRestHandlers, @@ -120,6 +122,8 @@ public RuntimeResourceDeployment(DeploymentInfo info, Supplier executo this.dynamicEntityWriter = dynamicEntityWriter; this.resourceLocatorHandler = resourceLocatorHandler; this.defaultBlocking = defaultBlocking; + this.blockingHandler = new BlockingHandler(executorSupplier); + this.responseWriterHandler = new ResponseWriterHandler(dynamicEntityWriter); } public RuntimeResource buildResourceMethod(ResourceClass clazz, @@ -181,7 +185,7 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, Optional blockingHandlerIndex = Optional.empty(); if (!defaultBlocking) { if (method.isBlocking()) { - handlers.add(new BlockingHandler(executorSupplier)); + handlers.add(blockingHandler); blockingHandlerIndex = Optional.of(handlers.size() - 1); score.add(ScoreSystem.Category.Execution, ScoreSystem.Diagnostic.ExecutionBlocking); } else { @@ -286,7 +290,7 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, boolean single = param.isSingle(); ParameterExtractor extractor = parameterExtractor(pathParameterIndexes, locatableResource, param.parameterType, param.type, param.name, - single, param.encoded, param.customerParameterExtractor); + single, param.encoded, param.customParameterExtractor); ParameterConverter converter = null; ParamConverterProviders paramConverterProviders = info.getParamConverterProviders(); boolean userProviderConvertersExist = !paramConverterProviders.getParamConverterProviders().isEmpty(); @@ -403,25 +407,25 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, //in future there will be one per filter List responseFilterHandlers; if (method.isSse()) { - handlers.add(new SseResponseWriterHandler()); + handlers.add(SseResponseWriterHandler.INSTANCE); responseFilterHandlers = Collections.emptyList(); } else { handlers.add(new ResponseHandler()); addHandlers(handlers, clazz, method, info, HandlerChainCustomizer.Phase.AFTER_RESPONSE_CREATED); responseFilterHandlers = new ArrayList<>(interceptorDeployment.setupResponseFilterHandler()); handlers.addAll(responseFilterHandlers); - handlers.add(new ResponseWriterHandler(dynamicEntityWriter)); + handlers.add(responseWriterHandler); } if (!clazz.resourceExceptionMapper().isEmpty() && (instanceHandler != null)) { // when class level exception mapper are used, we need to make sure that an instance of resource class exists // so we can invoke it abortHandlingChain.add(instanceHandler); } - abortHandlingChain.add(new ExceptionHandler()); - abortHandlingChain.add(new ResponseHandler()); + abortHandlingChain.add(ExceptionHandler.INSTANCE); + abortHandlingChain.add(ResponseHandler.INSTANCE); abortHandlingChain.addAll(responseFilterHandlers); - abortHandlingChain.add(new ResponseWriterHandler(dynamicEntityWriter)); + abortHandlingChain.add(responseWriterHandler); handlers.add(0, new AbortChainHandler(abortHandlingChain.toArray(EMPTY_REST_HANDLER_ARRAY))); return new RuntimeResource(method.getHttpMethod(), methodPathTemplate, @@ -431,7 +435,7 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz, clazz.getFactory(), handlers.toArray(EMPTY_REST_HANDLER_ARRAY), method.getName(), parameterDeclaredTypes, nonAsyncReturnType, method.isBlocking(), resourceClass, lazyMethod, - pathParameterIndexes, score, sseElementType, clazz.resourceExceptionMapper()); + pathParameterIndexes, info.isDevelopmentMode() ? score : null, sseElementType, clazz.resourceExceptionMapper()); } private boolean isSingleEffectiveWriter(List> buildTimeWriters) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ExceptionHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ExceptionHandler.java index dc6a06aa29c66..ad35f24963c55 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ExceptionHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ExceptionHandler.java @@ -8,6 +8,8 @@ */ public class ExceptionHandler implements ServerRestHandler { + public static final ExceptionHandler INSTANCE = new ExceptionHandler(); + @Override public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { requestContext.mapExceptionIfPresent(); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResponseHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResponseHandler.java index cf72e489bbbef..5f6787b0d571b 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResponseHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ResponseHandler.java @@ -19,6 +19,8 @@ */ public class ResponseHandler implements ServerRestHandler { + public static final ResponseHandler INSTANCE = new ResponseHandler(); + @Override public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { Object result = requestContext.getResult(); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/SseResponseWriterHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/SseResponseWriterHandler.java index 3352297a708ba..a1e5bb98820ac 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/SseResponseWriterHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/SseResponseWriterHandler.java @@ -8,6 +8,8 @@ */ public class SseResponseWriterHandler implements ServerRestHandler { + public static final SseResponseWriterHandler INSTANCE = new SseResponseWriterHandler(); + public SseResponseWriterHandler() { } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java index d0c82e7996bee..b502d94579bfe 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerMethodParameter.java @@ -1,5 +1,6 @@ package org.jboss.resteasy.reactive.server.model; +import java.util.Objects; import org.jboss.resteasy.reactive.common.model.MethodParameter; import org.jboss.resteasy.reactive.common.model.ParameterType; import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor; @@ -8,7 +9,7 @@ public class ServerMethodParameter extends MethodParameter { public ParameterConverterSupplier converter; - public ParameterExtractor customerParameterExtractor; + public ParameterExtractor customParameterExtractor; public ServerMethodParameter() { } @@ -16,13 +17,32 @@ public ServerMethodParameter() { public ServerMethodParameter(String name, String type, String declaredType, String declaredUnresolvedType, ParameterType parameterType, boolean single, String signature, - ParameterConverterSupplier converter, String defaultValue, boolean isObtainedAsCollection, boolean isOptional, + ParameterConverterSupplier converter, String defaultValue, boolean obtainedAsCollection, boolean optional, boolean encoded, - ParameterExtractor customerParameterExtractor) { + ParameterExtractor customParameterExtractor) { super(name, type, declaredType, declaredUnresolvedType, signature, parameterType, single, defaultValue, - isObtainedAsCollection, isOptional, + obtainedAsCollection, optional, encoded); this.converter = converter; - this.customerParameterExtractor = customerParameterExtractor; + this.customParameterExtractor = customParameterExtractor; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ServerMethodParameter that = (ServerMethodParameter) o; + if (!super.equals(that)) { + return false; + } + return Objects.equals(converter, that.converter) + && Objects.equals(customParameterExtractor, that.customParameterExtractor); + } + + @Override + public int hashCode() { + return Objects.hash(converter, customParameterExtractor) + super.hashCode(); } } diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerResourceMethod.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerResourceMethod.java index a341cbd878b5f..199c2282050ed 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerResourceMethod.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/model/ServerResourceMethod.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Set; import java.util.function.Supplier; +import org.jboss.resteasy.reactive.common.model.MethodParameter; import org.jboss.resteasy.reactive.common.model.ResourceMethod; import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor; import org.jboss.resteasy.reactive.server.spi.EndpointInvoker; @@ -17,6 +18,22 @@ public class ServerResourceMethod extends ResourceMethod { private List handlerChainCustomizers = new ArrayList<>(); private ParameterExtractor customerParameterExtractor; + public ServerResourceMethod() { + } + + public ServerResourceMethod(String httpMethod, String path, String[] produces, String sseElementType, String[] consumes, + Set nameBindingNames, String name, String returnType, String simpleReturnType, MethodParameter[] parameters, + boolean blocking, boolean suspended, boolean sse, boolean formParamRequired, boolean multipart, + List subResourceMethods, Supplier invoker, Set methodAnnotationNames, + List handlerChainCustomizers, ParameterExtractor customerParameterExtractor) { + super(httpMethod, path, produces, sseElementType, consumes, nameBindingNames, name, returnType, simpleReturnType, + parameters, blocking, suspended, sse, formParamRequired, multipart, subResourceMethods); + this.invoker = invoker; + this.methodAnnotationNames = methodAnnotationNames; + this.handlerChainCustomizers = handlerChainCustomizers; + this.customerParameterExtractor = customerParameterExtractor; + } + public Supplier getInvoker() { return invoker; }