diff --git a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java index 6ded609fea9b5..4a99224f09d27 100644 --- a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java +++ b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaProcessor.java @@ -1,9 +1,7 @@ package io.quarkus.amazon.lambda.deployment; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -16,14 +14,15 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; -import org.jboss.jandex.MethodInfo; import org.jboss.logging.Logger; import org.joda.time.DateTime; import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import io.quarkus.amazon.lambda.deployment.RequestHandlerJandexUtil.RequestHandlerJandexDefinition; import io.quarkus.amazon.lambda.runtime.AmazonLambdaRecorder; +import io.quarkus.amazon.lambda.runtime.AmazonLambdaRecorder.RequestHandlerDefinition; import io.quarkus.amazon.lambda.runtime.AmazonLambdaStaticRecorder; import io.quarkus.amazon.lambda.runtime.FunctionError; import io.quarkus.amazon.lambda.runtime.LambdaBuildTimeConfig; @@ -44,6 +43,7 @@ import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem; import io.quarkus.deployment.pkg.steps.NativeBuild; import io.quarkus.deployment.recording.RecorderContext; import io.quarkus.runtime.LaunchMode; @@ -52,8 +52,8 @@ public final class AmazonLambdaProcessor { public static final String AWS_LAMBDA_EVENTS_ARCHIVE_MARKERS = "com/amazonaws/services/lambda/runtime/events"; - private static final DotName REQUEST_HANDLER = DotName.createSimple(RequestHandler.class.getName()); - private static final DotName REQUEST_STREAM_HANDLER = DotName.createSimple(RequestStreamHandler.class.getName()); + private static final DotName REQUEST_HANDLER = DotName.createSimple(RequestHandler.class); + private static final DotName REQUEST_STREAM_HANDLER = DotName.createSimple(RequestStreamHandler.class); private static final DotName SKILL_STREAM_HANDLER = DotName.createSimple("com.amazon.ask.SkillStreamHandler"); private static final DotName NAMED = DotName.createSimple(Named.class.getName()); @@ -88,88 +88,51 @@ List discover(CombinedIndexBuildItem combinedIndexBuildIt BuildProducer reflectiveHierarchy, BuildProducer reflectiveClassBuildItemBuildProducer) throws BuildException { - List allKnownImplementors = new ArrayList<>( - combinedIndexBuildItem.getIndex().getAllKnownImplementors(REQUEST_HANDLER) + List requestHandlers = new ArrayList<>( + combinedIndexBuildItem.getIndex().getAllKnownImplementations(REQUEST_HANDLER) .stream().filter(INCLUDE_HANDLER_PREDICATE).toList()); - allKnownImplementors.addAll(combinedIndexBuildItem.getIndex() - .getAllKnownImplementors(REQUEST_STREAM_HANDLER).stream().filter(INCLUDE_HANDLER_PREDICATE).toList()); - allKnownImplementors.addAll(combinedIndexBuildItem.getIndex() + + List streamHandlers = new ArrayList<>(combinedIndexBuildItem.getIndex() + .getAllKnownImplementations(REQUEST_STREAM_HANDLER).stream().filter(INCLUDE_HANDLER_PREDICATE).toList()); + streamHandlers.addAll(combinedIndexBuildItem.getIndex() .getAllKnownSubclasses(SKILL_STREAM_HANDLER).stream().filter(INCLUDE_HANDLER_PREDICATE).toList()); - if (allKnownImplementors.size() > 0 && providedLambda.isPresent()) { + if ((!requestHandlers.isEmpty() || !streamHandlers.isEmpty()) && providedLambda.isPresent()) { throw new BuildException( "Multiple handler classes. You have a custom handler class and the " + providedLambda.get().getProvider() + " extension. Please remove one of them from your deployment.", - Collections.emptyList()); + List.of()); } - AdditionalBeanBuildItem.Builder builder = AdditionalBeanBuildItem.builder().setUnremovable(); - List ret = new ArrayList<>(); + AdditionalBeanBuildItem.Builder additionalBeansBuilder = AdditionalBeanBuildItem.builder().setUnremovable(); + List amazonLambdas = new ArrayList<>(); - for (ClassInfo info : allKnownImplementors) { - if (Modifier.isAbstract(info.flags())) { + for (ClassInfo requestHandler : requestHandlers) { + if (requestHandler.isAbstract()) { continue; } - final DotName name = info.name(); - final String lambda = name.toString(); - builder.addBeanClass(lambda); + additionalBeansBuilder.addBeanClass(requestHandler.name().toString()); + amazonLambdas.add(new AmazonLambdaBuildItem(requestHandler.name().toString(), getCdiName(requestHandler), false)); + } - String cdiName = null; - AnnotationInstance named = info.declaredAnnotation(NAMED); - if (named != null) { - cdiName = named.value().asString(); + for (ClassInfo streamHandler : streamHandlers) { + if (streamHandler.isAbstract()) { + continue; } - ClassInfo current = info; - boolean done = false; - boolean streamHandler = info.superName().equals(SKILL_STREAM_HANDLER); - while (current != null && !done) { - for (MethodInfo method : current.methods()) { - if (method.name().equals("handleRequest")) { - if (method.parametersCount() == 3) { - streamHandler = true; - done = true; - break; - } else if (method.parametersCount() == 2 - && !method.parameterType(0).name().equals(DotName.createSimple(Object.class.getName()))) { - String source = getClass().getSimpleName() + " > " + method.declaringClass() + "[" + method + "]"; - - reflectiveHierarchy.produce(ReflectiveHierarchyBuildItem - .builder(method.parameterType(0)) - .source(source) - .build()); - reflectiveHierarchy.produce(ReflectiveHierarchyBuildItem - .builder(method.returnType()) - .source(source) - .build()); - done = true; - break; - } - } - } - if (!done) { - current = combinedIndexBuildItem.getIndex().getClassByName(current.superName()); - } - } - if (done) { - String handlerClass = current.name().toString(); - ret.add(new AmazonLambdaBuildItem(handlerClass, cdiName, streamHandler)); - reflectiveClassBuildItemBuildProducer.produce(ReflectiveClassBuildItem.builder(handlerClass).methods() - .reason(getClass().getName() - + ": reflectively accessed in io.quarkus.amazon.lambda.runtime.AmazonLambdaRecorder.discoverHandlerMethod") - .build()); - } else { - // Fall back to the root implementor if a matching `handleRequest` is not found in the class hierarchy - ret.add(new AmazonLambdaBuildItem(lambda, cdiName, streamHandler)); - } + additionalBeansBuilder.addBeanClass(streamHandler.name().toString()); + amazonLambdas.add(new AmazonLambdaBuildItem(streamHandler.name().toString(), getCdiName(streamHandler), true)); } - additionalBeanBuildItemBuildProducer.produce(builder.build()); + + additionalBeanBuildItemBuildProducer.produce(additionalBeansBuilder.build()); + reflectiveClassBuildItemBuildProducer .produce(ReflectiveClassBuildItem.builder(FunctionError.class).methods().fields() .reason(getClass().getName()) .build()); - return ret; + + return amazonLambdas; } @BuildStep @@ -218,11 +181,14 @@ void processProvidedLambda(Optional provid @BuildStep @Record(ExecutionTime.STATIC_INIT) - public void recordStaticInitHandlerClass(List lambdas, + public void recordStaticInitHandlerClass(CombinedIndexBuildItem index, + List lambdas, LambdaObjectMapperInitializedBuildItem mapper, // ordering! Optional providedLambda, AmazonLambdaStaticRecorder recorder, - RecorderContext context) { + RecorderContext context, + BuildProducer reflectiveMethods, + BuildProducer reflectiveHierarchies) { // can set handler within static initialization if only one handler exists in deployment if (providedLambda.isPresent()) { boolean useStreamHandler = false; @@ -238,10 +204,10 @@ public void recordStaticInitHandlerClass(List lambdas, .classProxy(providedLambda.get().getHandlerClass().getName()); recorder.setStreamHandlerClass(handlerClass); } else { - Class> handlerClass = (Class>) context - .classProxy(providedLambda.get().getHandlerClass().getName()); - - recorder.setHandlerClass(handlerClass); + RequestHandlerJandexDefinition requestHandlerJandexDefinition = RequestHandlerJandexUtil + .discoverHandlerMethod(providedLambda.get().getHandlerClass().getName(), index.getComputingIndex()); + registerForReflection(requestHandlerJandexDefinition, reflectiveMethods, reflectiveHierarchies); + recorder.setHandlerClass(toRequestHandlerDefinition(requestHandlerJandexDefinition, context)); } } else if (lambdas != null && lambdas.size() == 1) { AmazonLambdaBuildItem item = lambdas.get(0); @@ -251,11 +217,10 @@ public void recordStaticInitHandlerClass(List lambdas, recorder.setStreamHandlerClass(handlerClass); } else { - Class> handlerClass = (Class>) context - .classProxy(item.getHandlerClass()); - - recorder.setHandlerClass(handlerClass); - + RequestHandlerJandexDefinition requestHandlerJandexDefinition = RequestHandlerJandexUtil + .discoverHandlerMethod(item.getHandlerClass(), index.getComputingIndex()); + registerForReflection(requestHandlerJandexDefinition, reflectiveMethods, reflectiveHierarchies); + recorder.setHandlerClass(toRequestHandlerDefinition(requestHandlerJandexDefinition, context)); } } else if (lambdas == null || lambdas.isEmpty()) { String errorMessage = "Unable to find handler class, make sure your deployment includes a single " @@ -276,17 +241,20 @@ public void recordBeanContainer(BeanContainerBuildItem beanContainerBuildItem, @BuildStep @Record(ExecutionTime.RUNTIME_INIT) - public void recordHandlerClass(List lambdas, + public void recordHandlerClass(CombinedIndexBuildItem index, + List lambdas, Optional providedLambda, BeanContainerBuildItem beanContainerBuildItem, AmazonLambdaRecorder recorder, List orderServicesFirst, // try to order this after service recorders - RecorderContext context) { + RecorderContext context, + BuildProducer reflectiveMethods, + BuildProducer reflectiveHierarchies) { // Have to set lambda class at runtime if there is not a provided lambda or there is more than one lambda in // deployment if (!providedLambda.isPresent() && lambdas != null && lambdas.size() > 1) { - List>> unnamed = new ArrayList<>(); - Map>> named = new HashMap<>(); + List unnamed = new ArrayList<>(); + Map named = new HashMap<>(); List> unnamedStreamHandler = new ArrayList<>(); Map> namedStreamHandler = new HashMap<>(); @@ -302,9 +270,15 @@ public void recordHandlerClass(List lambdas, } } else { if (i.getName() == null) { - unnamed.add((Class>) context.classProxy(i.getHandlerClass())); + RequestHandlerJandexDefinition requestHandlerJandexDefinition = RequestHandlerJandexUtil + .discoverHandlerMethod(i.getHandlerClass(), index.getComputingIndex()); + registerForReflection(requestHandlerJandexDefinition, reflectiveMethods, reflectiveHierarchies); + unnamed.add(toRequestHandlerDefinition(requestHandlerJandexDefinition, context)); } else { - named.put(i.getName(), (Class>) context.classProxy(i.getHandlerClass())); + RequestHandlerJandexDefinition requestHandlerJandexDefinition = RequestHandlerJandexUtil + .discoverHandlerMethod(i.getHandlerClass(), index.getComputingIndex()); + registerForReflection(requestHandlerJandexDefinition, reflectiveMethods, reflectiveHierarchies); + named.put(i.getName(), toRequestHandlerDefinition(requestHandlerJandexDefinition, context)); } } } @@ -352,4 +326,45 @@ void recordExpectedExceptions(LambdaBuildTimeConfig config, recorder.setExpectedExceptionClasses(classes); } + private static String getCdiName(ClassInfo handler) { + AnnotationInstance named = handler.declaredAnnotation(NAMED); + if (named == null) { + return null; + } + return named.value().asString(); + } + + private static void registerForReflection(RequestHandlerJandexDefinition requestHandlerJandexDefinition, + BuildProducer reflectiveMethods, + BuildProducer reflectiveHierarchies) { + String source = AmazonLambdaProcessor.class.getSimpleName() + " > " + + requestHandlerJandexDefinition.method().declaringClass() + "[" + requestHandlerJandexDefinition.method() + + "]"; + reflectiveHierarchies.produce(ReflectiveHierarchyBuildItem + .builder(requestHandlerJandexDefinition.inputOutputTypes().inputType()) + .source(source) + .build()); + reflectiveHierarchies.produce(ReflectiveHierarchyBuildItem + .builder(requestHandlerJandexDefinition.inputOutputTypes().outputType()) + .source(source) + .build()); + if (requestHandlerJandexDefinition.inputOutputTypes().isCollection()) { + reflectiveMethods.produce(new ReflectiveMethodBuildItem( + "method reflectively accessed in io.quarkus.amazon.lambda.runtime.AmazonLambdaRecorder.discoverHandlerMethod", + requestHandlerJandexDefinition.method())); + reflectiveHierarchies.produce(ReflectiveHierarchyBuildItem + .builder(requestHandlerJandexDefinition.inputOutputTypes().elementType()) + .source(source) + .build()); + } + } + + private static RequestHandlerDefinition toRequestHandlerDefinition(RequestHandlerJandexDefinition jandexDefinition, + RecorderContext context) { + return new RequestHandlerDefinition( + (Class>) context.classProxy(jandexDefinition.handlerClass().name().toString()), + context.classProxy(jandexDefinition.method().declaringClass().name().toString()), + context.classProxy(jandexDefinition.inputOutputTypes().inputType().name().toString()), + context.classProxy(jandexDefinition.inputOutputTypes().outputType().name().toString())); + } } diff --git a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/RequestHandlerJandexUtil.java b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/RequestHandlerJandexUtil.java new file mode 100644 index 0000000000000..a93a8d45356c7 --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/RequestHandlerJandexUtil.java @@ -0,0 +1,269 @@ +package io.quarkus.amazon.lambda.deployment; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.ParameterizedType; +import org.jboss.jandex.Type; +import org.jboss.jandex.TypeVariable; + +import com.amazonaws.services.lambda.runtime.RequestHandler; + +public class RequestHandlerJandexUtil { + + private static final DotName REQUEST_HANDLER = DotName.createSimple(RequestHandler.class); + private static final DotName OBJECT = DotName.createSimple("java.lang.Object"); + private static final DotName COLLECTION = DotName.createSimple(Collection.class); + + private RequestHandlerJandexUtil() { + } + + public static RequestHandlerJandexDefinition discoverHandlerMethod(String handlerClassName, IndexView index) { + ClassInfo handlerClass = index.getClassByName(handlerClassName); + if (handlerClass == null) { + throw new IllegalArgumentException("RequestHandler class not found in the index: " + handlerClassName); + } + + MethodInfo concreteHandleRequestMethod = findConcreteHandleRequestMethod(handlerClass, index); + if (concreteHandleRequestMethod == null) { + throw new IllegalStateException( + "Unable to find a concrete handleRequest method on handler class " + handlerClass.name()); + } + + Map typeMap = new HashMap<>(); + InputOutputTypes inputOutputTypes = resolveInputOutputTypes(handlerClass.name(), index, typeMap); + if (isUnresolved(inputOutputTypes)) { + throw new IllegalStateException( + "Unable to resolve input and output types for handler class " + handlerClass.name()); + } + + return new RequestHandlerJandexDefinition(handlerClass, concreteHandleRequestMethod, inputOutputTypes); + } + + private static MethodInfo findConcreteHandleRequestMethod(ClassInfo handlerClass, IndexView index) { + ClassInfo currentClass = handlerClass; + + // Look for implementations of the method in the class hierarchy + while (currentClass != null && !OBJECT.equals(currentClass.name())) { + for (MethodInfo method : currentClass.methods()) { + if (isHandleRequestMethod(method) && !method.isSynthetic() && !method.isAbstract()) { + return method; + } + } + + Type superType = currentClass.superClassType(); + if (superType != null) { + currentClass = index.getClassByName(superType.name()); + } else { + currentClass = null; + } + } + + // If not found, look for default methods in interfaces + currentClass = handlerClass; + while (currentClass != null && !OBJECT.equals(currentClass.name())) { + for (Type ifaceType : currentClass.interfaceTypes()) { + MethodInfo defaultMethod = findDefaultInterfaceMethod(ifaceType.name(), index); + if (defaultMethod != null) { + return defaultMethod; + } + } + + // Move to superclass + Type superType = currentClass.superClassType(); + if (superType != null) { + currentClass = index.getClassByName(superType.name()); + } else { + currentClass = null; + } + } + + return null; + } + + private static MethodInfo findDefaultInterfaceMethod(DotName ifaceName, IndexView index) { + ClassInfo iface = index.getClassByName(ifaceName); + if (iface == null) { + return null; + } + + for (MethodInfo method : iface.methods()) { + if (isHandleRequestMethod(method) && method.isDefault()) { + return method; + } + } + + for (Type parentIfaceType : iface.interfaceTypes()) { + MethodInfo defaultMethod = findDefaultInterfaceMethod(parentIfaceType.name(), index); + if (defaultMethod != null) { + return defaultMethod; + } + } + + return null; + } + + private static boolean isHandleRequestMethod(MethodInfo method) { + return method.name().equals("handleRequest") && method.parametersCount() == 2; + } + + private static boolean isCollectionType(DotName typeName, IndexView index) { + if (COLLECTION.equals(typeName)) { + return true; + } + + ClassInfo classInfo = index.getClassByName(typeName); + if (classInfo == null) { + return false; + } + + for (Type interfaceType : classInfo.interfaceTypes()) { + if (isCollectionType(interfaceType.name(), index)) { + return true; + } + } + + Type superType = classInfo.superClassType(); + if (superType != null) { + if (isCollectionType(superType.name(), index)) { + return true; + } + } + + return false; + } + + public record InputOutputTypes(Type inputType, boolean isCollection, Type elementType, Type outputType) { + private static final InputOutputTypes UNRESOLVED = new InputOutputTypes( + Type.create(OBJECT, Type.Kind.CLASS), false, null, + Type.create(OBJECT, Type.Kind.CLASS)); + } + + public record RequestHandlerJandexDefinition(ClassInfo handlerClass, MethodInfo method, InputOutputTypes inputOutputTypes) { + } + + private static boolean isUnresolved(InputOutputTypes types) { + return types == null || types == InputOutputTypes.UNRESOLVED; + } + + private static InputOutputTypes resolveInputOutputTypes(DotName className, IndexView index, + Map typeMap) { + ClassInfo classInfo = index.getClassByName(className); + if (classInfo == null) { + return null; + } + + for (Type interfaceType : classInfo.interfaceTypes()) { + InputOutputTypes result = resolveInputOutputTypesFromType(interfaceType, index, typeMap); + if (result != null) { + return result; + } + } + + Type superType = classInfo.superClassType(); + if (superType != null) { + InputOutputTypes result = resolveInputOutputTypesFromType(superType, index, typeMap); + if (result != null) { + return result; + } + } + + return null; + } + + private static InputOutputTypes resolveInputOutputTypesFromType(Type currentType, IndexView index, + Map typeMap) { + if (currentType.kind() == Type.Kind.PARAMETERIZED_TYPE) { + ParameterizedType pt = currentType.asParameterizedType(); + DotName rawName = pt.name(); + + // If we hit RequestHandler, resolve the type arguments + if (REQUEST_HANDLER.equals(rawName)) { + List args = pt.arguments(); + Type inputType = resolveTypeArgument(args.get(0), typeMap); + Type outputType = resolveTypeArgument(args.get(1), typeMap); + + // Check if input type is a collection and extract element type + boolean isCollection = false; + Type elementType = null; + Type rawInputType = args.get(0); + + if (rawInputType.kind() == Type.Kind.PARAMETERIZED_TYPE) { + ParameterizedType inputPt = rawInputType.asParameterizedType(); + if (isCollectionType(inputPt.name(), index)) { + isCollection = true; + if (!inputPt.arguments().isEmpty()) { + elementType = resolveTypeArgument(inputPt.arguments().get(0), typeMap); + } + } + } else if (rawInputType.kind() == Type.Kind.CLASS) { + if (isCollectionType(rawInputType.name(), index)) { + isCollection = true; + elementType = Type.create(OBJECT, Type.Kind.CLASS); + } + } + + return new InputOutputTypes(inputType, isCollection, elementType, outputType); + } + + // Record bindings for current type variables + ClassInfo rawClass = index.getClassByName(rawName); + if (rawClass != null) { + List vars = rawClass.typeParameters(); + List args = pt.arguments(); + Map newTypeMap = new HashMap<>(typeMap); + for (int i = 0; i < vars.size() && i < args.size(); i++) { + newTypeMap.put(vars.get(i).identifier(), args.get(i)); + } + + // Recursively check this type's hierarchy + InputOutputTypes result = resolveInputOutputTypes(rawName, index, newTypeMap); + if (result != null) { + return result; + } + } + } else if (currentType.kind() == Type.Kind.CLASS) { + DotName className = currentType.name(); + + if (REQUEST_HANDLER.equals(className)) { + return InputOutputTypes.UNRESOLVED; + } + + return resolveInputOutputTypes(className, index, typeMap); + } + + return null; + } + + private static Type resolveTypeArgument(Type type, Map typeMap) { + if (type.kind() == Type.Kind.TYPE_VARIABLE) { + TypeVariable tv = type.asTypeVariable(); + Type resolved = typeMap.get(tv.identifier()); + if (resolved != null) { + // Recursively resolve in case the resolved type is also a type variable + return resolveTypeArgument(resolved, typeMap); + } + // If we can't resolve the type variable, get its first bound + return getTypeFromBounds(tv); + } + return type; + } + + private static Type getTypeFromBounds(TypeVariable tv) { + List bounds = tv.bounds(); + if (!bounds.isEmpty()) { + Type firstBound = bounds.get(0); + if (firstBound.kind() == Type.Kind.PARAMETERIZED_TYPE) { + return Type.create(firstBound.name(), Type.Kind.CLASS); + } + return firstBound; + } + return Type.create(OBJECT, Type.Kind.CLASS); + } +} diff --git a/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/RequestHandlerJandexUtilTest.java b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/RequestHandlerJandexUtilTest.java new file mode 100644 index 0000000000000..26da8b9cd04e9 --- /dev/null +++ b/extensions/amazon-lambda/deployment/src/test/java/io/quarkus/amazon/lambda/deployment/RequestHandlerJandexUtilTest.java @@ -0,0 +1,542 @@ +package io.quarkus.amazon.lambda.deployment; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.jboss.jandex.IndexView; +import org.jboss.jandex.Indexer; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestHandler; + +import io.quarkus.amazon.lambda.deployment.RequestHandlerJandexUtil.RequestHandlerJandexDefinition; +import io.quarkus.deployment.index.IndexWrapper; +import io.quarkus.deployment.index.PersistentClassIndex; + +public class RequestHandlerJandexUtilTest { + + private static IndexView index; + + @BeforeAll + public static void setup() throws IOException { + Indexer indexer = new Indexer(); + + // Mimick what would happen in a regular Quarkus app: + // forcefully index all test classes, but let the computing index index JDK classes + indexer.indexClass(SimpleStringHandler.class); + indexer.indexClass(SimpleIntegerHandler.class); + indexer.indexClass(BaseHandler.class); + indexer.indexClass(MiddleHandler.class); + indexer.indexClass(ConcreteHandler.class); + indexer.indexClass(CustomInterface.class); + indexer.indexClass(InterfaceBasedHandler.class); + indexer.indexClass(MultiLevelBase.class); + indexer.indexClass(MultiLevelMiddle.class); + indexer.indexClass(MultiLevelConcrete.class); + indexer.indexClass(InvertedBase.class); + indexer.indexClass(InvertedMiddle.class); + indexer.indexClass(InvertedConcrete.class); + indexer.indexClass(ComplexInvertedBase.class); + indexer.indexClass(ComplexInvertedMiddle.class); + indexer.indexClass(ComplexInvertedConcrete.class); + indexer.indexClass(ResolvedObjectHandler.class); + indexer.indexClass(AbstractParentWithAbstract.class); + indexer.indexClass(ConcreteOverridesAbstract.class); + indexer.indexClass(DefaultMethodInterface.class); + indexer.indexClass(DefaultMethodHandler.class); + indexer.indexClass(ConcreteOverridesDefault.class); + indexer.indexClass(ConcreteParent.class); + indexer.indexClass(ChildInheritsFromConcrete.class); + indexer.indexClass(AbstractWithConcrete.class); + indexer.indexClass(InheritsFromAbstractWithConcrete.class); + indexer.indexClass(PurelyAbstractHandler.class); + indexer.indexClass(BaseInterfaceNoDefault.class); + indexer.indexClass(NestedDefaultInterface.class); + indexer.indexClass(NestedInterfaceHandler.class); + + // Collection test classes + indexer.indexClass(ListStringHandler.class); + indexer.indexClass(ListIntegerHandler.class); + indexer.indexClass(SetStringHandler.class); + indexer.indexClass(CollectionDoubleHandler.class); + indexer.indexClass(GenericListBase.class); + indexer.indexClass(GenericListConcrete.class); + + index = new IndexWrapper(indexer.complete(), Thread.currentThread().getContextClassLoader(), + new PersistentClassIndex()); + } + + private static void indexClass(Indexer indexer, Class clazz) throws IOException { + indexer.indexClass(clazz); + } + + @Test + public void testSimpleStringHandler() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(SimpleStringHandler.class.getName(), index); + + assertNotNull(definition); + assertEquals(SimpleStringHandler.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertEquals(String.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(Integer.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testSimpleIntegerHandler() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(SimpleIntegerHandler.class.getName(), index); + + assertNotNull(definition); + assertEquals(SimpleIntegerHandler.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertEquals(Integer.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(Boolean.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testConcreteHandler() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(ConcreteHandler.class.getName(), index); + + assertNotNull(definition); + assertEquals(ConcreteHandler.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertEquals(Integer.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(String.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testInterfaceBasedHandler() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(InterfaceBasedHandler.class.getName(), index); + + assertNotNull(definition); + assertEquals(InterfaceBasedHandler.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertEquals(String.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(Boolean.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testMultiLevelConcrete() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(MultiLevelConcrete.class.getName(), index); + + assertNotNull(definition); + assertEquals(MultiLevelConcrete.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertEquals(Double.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(Float.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testInvertedConcrete() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(InvertedConcrete.class.getName(), index); + + assertNotNull(definition); + assertEquals(InvertedConcrete.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertEquals(Integer.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(String.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testComplexInvertedConcrete() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(ComplexInvertedConcrete.class.getName(), index); + + assertNotNull(definition); + assertEquals(ComplexInvertedConcrete.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertEquals(Long.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(Boolean.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testObjectHandlerUsesObjectTypes() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(ResolvedObjectHandler.class.getName(), index); + + assertNotNull(definition); + assertEquals(ResolvedObjectHandler.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertEquals(Object.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(Object.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testConcreteMethodPrefersOverAbstractParent() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(ConcreteOverridesAbstract.class.getName(), index); + + assertNotNull(definition); + assertEquals("handleRequest", definition.method().name()); + assertEquals(String.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(Boolean.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + // Should find the concrete method in the child class, not the abstract one in parent + assertEquals(ConcreteOverridesAbstract.class.getName(), definition.method().declaringClass().name().toString()); + } + + @Test + public void testDefaultInterfaceMethod() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(DefaultMethodHandler.class.getName(), index); + + assertNotNull(definition); + assertEquals("handleRequest", definition.method().name()); + assertEquals(Double.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(Long.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + // Should find the default method in the interface + assertEquals(DefaultMethodInterface.class.getName(), definition.method().declaringClass().name().toString()); + } + + @Test + public void testConcreteMethodPrefersOverDefaultMethod() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(ConcreteOverridesDefault.class.getName(), index); + + assertNotNull(definition); + assertEquals("handleRequest", definition.method().name()); + assertEquals(Double.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(Long.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + // Should prefer the concrete implementation over the default method + assertEquals(ConcreteOverridesDefault.class.getName(), definition.method().declaringClass().name().toString()); + } + + @Test + public void testInheritsConcreteFromParent() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(ChildInheritsFromConcrete.class.getName(), index); + + assertNotNull(definition); + assertEquals("handleRequest", definition.method().name()); + assertEquals(Integer.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(String.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + // Should find the concrete method in the parent class + assertEquals(ConcreteParent.class.getName(), definition.method().declaringClass().name().toString()); + } + + @Test + public void testAbstractClassWithConcreteMethod() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(InheritsFromAbstractWithConcrete.class.getName(), index); + + assertNotNull(definition); + assertEquals("handleRequest", definition.method().name()); + assertEquals(String.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(String.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + // Should find the concrete method in the abstract parent + assertEquals(AbstractWithConcrete.class.getName(), definition.method().declaringClass().name().toString()); + } + + @Test + public void testPurelyAbstractShouldFail() { + assertThrows(IllegalStateException.class, () -> { + RequestHandlerJandexUtil.discoverHandlerMethod(PurelyAbstractHandler.class.getName(), index); + }); + } + + @Test + public void testNestedInterfaceDefault() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(NestedInterfaceHandler.class.getName(), index); + + assertNotNull(definition); + assertEquals("handleRequest", definition.method().name()); + assertEquals(Float.class.getName(), definition.inputOutputTypes().inputType().name().toString()); + assertEquals(Byte.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + // Should find the default method in the nested interface + assertEquals(NestedDefaultInterface.class.getName(), definition.method().declaringClass().name().toString()); + } + + // Collection handler tests + @Test + public void testListStringHandler() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(ListStringHandler.class.getName(), index); + + assertNotNull(definition); + assertEquals(ListStringHandler.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertTrue(definition.inputOutputTypes().isCollection(), "Input type should be detected as collection"); + assertEquals(String.class.getName(), definition.inputOutputTypes().elementType().name().toString()); + assertEquals(Integer.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testListIntegerHandler() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(ListIntegerHandler.class.getName(), index); + + assertNotNull(definition); + assertEquals(ListIntegerHandler.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertTrue(definition.inputOutputTypes().isCollection(), "Input type should be detected as collection"); + assertEquals(Integer.class.getName(), definition.inputOutputTypes().elementType().name().toString()); + assertEquals(String.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testSetStringHandler() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(SetStringHandler.class.getName(), index); + + assertNotNull(definition); + assertEquals(SetStringHandler.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertTrue(definition.inputOutputTypes().isCollection(), "Input type should be detected as collection"); + assertEquals(String.class.getName(), definition.inputOutputTypes().elementType().name().toString()); + assertEquals(Boolean.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testCollectionDoubleHandler() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(CollectionDoubleHandler.class.getName(), index); + + assertNotNull(definition); + assertEquals(CollectionDoubleHandler.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertTrue(definition.inputOutputTypes().isCollection(), "Input type should be detected as collection"); + assertEquals(Double.class.getName(), definition.inputOutputTypes().elementType().name().toString()); + assertEquals(Long.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testGenericListConcrete() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(GenericListConcrete.class.getName(), index); + + assertNotNull(definition); + assertEquals(GenericListConcrete.class.getName(), definition.method().declaringClass().name().toString()); + assertEquals("handleRequest", definition.method().name()); + assertTrue(definition.inputOutputTypes().isCollection(), "Input type should be detected as collection"); + assertEquals(String.class.getName(), definition.inputOutputTypes().elementType().name().toString()); + assertEquals(Float.class.getName(), definition.inputOutputTypes().outputType().name().toString()); + } + + @Test + public void testNonCollectionHandlerNotDetectedAsCollection() { + RequestHandlerJandexDefinition definition = RequestHandlerJandexUtil + .discoverHandlerMethod(SimpleStringHandler.class.getName(), index); + + assertNotNull(definition); + assertFalse(definition.inputOutputTypes().isCollection(), "Non-collection input should not be detected as collection"); + } + + // Simple hierarchy test cases + public static class SimpleStringHandler implements RequestHandler { + @Override + public Integer handleRequest(String input, Context context) { + return input.length(); + } + } + + public static class SimpleIntegerHandler implements RequestHandler { + @Override + public Boolean handleRequest(Integer input, Context context) { + return input > 0; + } + } + + // Complex hierarchy test cases + public static abstract class BaseHandler implements RequestHandler { + } + + public static abstract class MiddleHandler extends BaseHandler { + } + + public static class ConcreteHandler extends MiddleHandler { + @Override + public String handleRequest(Integer input, Context context) { + return String.valueOf(input); + } + } + + public static interface CustomInterface extends RequestHandler { + } + + public static class InterfaceBasedHandler implements CustomInterface { + @Override + public Boolean handleRequest(String input, Context context) { + return Boolean.valueOf(input); + } + } + + public static abstract class MultiLevelBase implements RequestHandler { + } + + public static abstract class MultiLevelMiddle extends MultiLevelBase { + } + + public static class MultiLevelConcrete extends MultiLevelMiddle { + @Override + public Float handleRequest(Double input, Context context) { + return input.floatValue(); + } + } + + // Inverted type parameters test cases + public static abstract class InvertedBase implements RequestHandler { + } + + public static abstract class InvertedMiddle extends InvertedBase { // Note: inverted order + } + + public static class InvertedConcrete extends InvertedMiddle { + @Override + public String handleRequest(Integer input, Context context) { + return String.valueOf(input); + } + } + + public static abstract class ComplexInvertedBase implements RequestHandler { // Y, X instead of X, Y + } + + public static abstract class ComplexInvertedMiddle extends ComplexInvertedBase { // B, A instead of A, B + } + + public static class ComplexInvertedConcrete extends ComplexInvertedMiddle { + @Override + public Boolean handleRequest(Long input, Context context) { + return input > 0; + } + } + + public static class ResolvedObjectHandler implements RequestHandler { + @Override + public Object handleRequest(Object input, Context context) { + return input; + } + } + + // Abstract parent with concrete child override + public static abstract class AbstractParentWithAbstract implements RequestHandler { + // Abstract method - should be ignored + public abstract Boolean handleRequest(String input, Context context); + } + + public static class ConcreteOverridesAbstract extends AbstractParentWithAbstract { + @Override + public Boolean handleRequest(String input, Context context) { + return Boolean.valueOf(input); + } + } + + // Default interface method test + public interface DefaultMethodInterface extends RequestHandler { + @Override + default Long handleRequest(Double input, Context context) { + return input.longValue(); + } + } + + public static class DefaultMethodHandler implements DefaultMethodInterface { + // Uses the default implementation from interface + } + + // Concrete method overrides default method + public static class ConcreteOverridesDefault implements DefaultMethodInterface { + @Override + public Long handleRequest(Double input, Context context) { + return input.longValue() * 2; // Different implementation + } + } + + // Concrete parent with inheriting child + public static class ConcreteParent implements RequestHandler { + @Override + public String handleRequest(Integer input, Context context) { + return String.valueOf(input); + } + } + + public static class ChildInheritsFromConcrete extends ConcreteParent { + // Inherits the concrete handleRequest method from parent + } + + // Abstract class with concrete method + public static abstract class AbstractWithConcrete implements RequestHandler { + @Override + public String handleRequest(String input, Context context) { + return input.toUpperCase(); + } + } + + public static class InheritsFromAbstractWithConcrete extends AbstractWithConcrete { + // Inherits concrete method from abstract parent + } + + // Purely abstract - should fail + public static abstract class PurelyAbstractHandler implements RequestHandler { + // Only has abstract methods - should fail + } + + // Nested interface default method + public interface BaseInterfaceNoDefault extends RequestHandler { + // No default method here + } + + public interface NestedDefaultInterface extends BaseInterfaceNoDefault { + @Override + default Byte handleRequest(Float input, Context context) { + return input.byteValue(); + } + } + + public static class NestedInterfaceHandler implements NestedDefaultInterface { + // Uses the nested default method + } + + // Collection test handler classes + public static class ListStringHandler implements RequestHandler, Integer> { + @Override + public Integer handleRequest(List input, Context context) { + return input.size(); + } + } + + public static class ListIntegerHandler implements RequestHandler, String> { + @Override + public String handleRequest(List input, Context context) { + return input.toString(); + } + } + + public static class SetStringHandler implements RequestHandler, Boolean> { + @Override + public Boolean handleRequest(Set input, Context context) { + return !input.isEmpty(); + } + } + + public static class CollectionDoubleHandler implements RequestHandler, Long> { + @Override + public Long handleRequest(Collection input, Context context) { + return (long) input.size(); + } + } + + // Generic list with type resolution + public static abstract class GenericListBase implements RequestHandler, Float> { + } + + public static class GenericListConcrete extends GenericListBase { + @Override + public Float handleRequest(List input, Context context) { + return (float) input.size(); + } + } +} diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java index d854f8c062846..72bfcf5344a0c 100644 --- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaRecorder.java @@ -46,21 +46,26 @@ public void setStreamHandlerClass(Class handler) streamHandlerClass = handler; } - static void initializeHandlerClass(Class> handler) { - handlerClass = handler; + static void initializeHandlerClass(RequestHandlerDefinition requestHandlerDefinition) { + handlerClass = requestHandlerDefinition.handlerClass(); ObjectMapper objectMapper = AmazonLambdaMapperRecorder.objectMapper; - Method handlerMethod = discoverHandlerMethod(handlerClass); - Class parameterType = handlerMethod.getParameterTypes()[0]; - if (parameterType.equals(S3Event.class)) { + if (requestHandlerDefinition.inputType().equals(S3Event.class)) { objectReader = new S3EventInputReader(objectMapper); - } else if (Collection.class.isAssignableFrom(parameterType)) { - objectReader = new CollectionInputReader<>(objectMapper, handlerMethod); + } else if (Collection.class.isAssignableFrom(requestHandlerDefinition.inputType())) { + // we have to use reflection to figure out the element generic type + try { + Method handleRequestMethod = requestHandlerDefinition.handleRequestMethodHostClass().getMethod("handleRequest", + requestHandlerDefinition.inputType(), Context.class); + objectReader = new CollectionInputReader<>(objectMapper, handleRequestMethod.getGenericParameterTypes()[0]); + } catch (Exception e) { + throw new IllegalStateException("Unable to find handleRequest method in " + handlerClass.getName(), e); + } } else { - objectReader = new JacksonInputReader(objectMapper.readerFor(parameterType)); + objectReader = new JacksonInputReader(objectMapper.readerFor(requestHandlerDefinition.inputType())); } - objectWriter = new JacksonOutputWriter(objectMapper.writerFor(handlerMethod.getReturnType())); + objectWriter = new JacksonOutputWriter(objectMapper.writerFor(requestHandlerDefinition.outputType())); } public void setBeanContainer(BeanContainer container) { @@ -87,62 +92,40 @@ public static void handle(InputStream inputStream, OutputStream outputStream, Co } } - private static Method discoverHandlerMethod(Class> handlerClass) { - final Method[] methods = handlerClass.getDeclaredMethods(); - Method method = null; - for (int i = 0; i < methods.length && method == null; i++) { - if (methods[i].getName().equals("handleRequest")) { - if (methods[i].getParameterCount() == 2) { - final Class[] types = methods[i].getParameterTypes(); - if (!types[0].equals(Object.class)) { - method = methods[i]; - } - } - } - } - if (method == null && methods.length > 0) { - method = methods[0]; - } - if (method == null) { - throw new RuntimeException("Unable to find a method which handles request on handler class " + handlerClass); - } - return method; - } - - public void chooseHandlerClass(List>> unnamedHandlerClasses, - Map>> namedHandlerClasses, + public void chooseHandlerClass(List unnamedHandlerDefinitions, + Map namedHandlerDefinitions, List> unnamedStreamHandlerClasses, Map> namedStreamHandlerClasses) { - Class> handlerClass = null; + RequestHandlerDefinition handlerDefinition = null; Class handlerStreamClass = null; if (runtimeConfig.getValue().handler().isPresent()) { - handlerClass = namedHandlerClasses.get(runtimeConfig.getValue().handler().get()); + handlerDefinition = namedHandlerDefinitions.get(runtimeConfig.getValue().handler().get()); handlerStreamClass = namedStreamHandlerClasses.get(runtimeConfig.getValue().handler().get()); - if (handlerClass == null && handlerStreamClass == null) { + if (handlerDefinition == null && handlerStreamClass == null) { String errorMessage = "Unable to find handler class with name " + runtimeConfig.getValue().handler().get() + " make sure there is a handler class in the deployment with the correct @Named annotation"; - throw new RuntimeException(errorMessage); + throw new IllegalStateException(errorMessage); } } else { - int unnamedTotal = unnamedHandlerClasses.size() + unnamedStreamHandlerClasses.size(); - int namedTotal = namedHandlerClasses.size() + namedStreamHandlerClasses.size(); + int unnamedTotal = unnamedHandlerDefinitions.size() + unnamedStreamHandlerClasses.size(); + int namedTotal = namedHandlerDefinitions.size() + namedStreamHandlerClasses.size(); if (unnamedTotal > 1 || namedTotal > 1 || (unnamedTotal > 0 && namedTotal > 0)) { String errorMessage = "Multiple handler classes, either specify the quarkus.lambda.handler property, or make sure there is only a single " + RequestHandler.class.getName() + " or, " + RequestStreamHandler.class.getName() + " implementation in the deployment"; - throw new RuntimeException(errorMessage); + throw new IllegalStateException(errorMessage); } else if (unnamedTotal == 0 && namedTotal == 0) { String errorMessage = "Unable to find handler class, make sure your deployment includes a single " + RequestHandler.class.getName() + " or, " + RequestStreamHandler.class.getName() + " implementation"; - throw new RuntimeException(errorMessage); + throw new IllegalStateException(errorMessage); } else if ((unnamedTotal + namedTotal) == 1) { - if (!unnamedHandlerClasses.isEmpty()) { - handlerClass = unnamedHandlerClasses.get(0); - } else if (!namedHandlerClasses.isEmpty()) { - handlerClass = namedHandlerClasses.values().iterator().next(); + if (!unnamedHandlerDefinitions.isEmpty()) { + handlerDefinition = unnamedHandlerDefinitions.get(0); + } else if (!namedHandlerDefinitions.isEmpty()) { + handlerDefinition = namedHandlerDefinitions.values().iterator().next(); } else if (!unnamedStreamHandlerClasses.isEmpty()) { handlerStreamClass = unnamedStreamHandlerClasses.get(0); } else if (!namedStreamHandlerClasses.isEmpty()) { @@ -154,7 +137,7 @@ public void chooseHandlerClass(List>> unnam if (handlerStreamClass != null) { setStreamHandlerClass(handlerStreamClass); } else { - initializeHandlerClass(handlerClass); + initializeHandlerClass(handlerDefinition); } } @@ -200,4 +183,8 @@ protected boolean shouldLog(Exception e) { loop.startPollLoop(context); } + + public record RequestHandlerDefinition(Class> handlerClass, + Class handleRequestMethodHostClass, Class inputType, Class outputType) { + } } diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaStaticRecorder.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaStaticRecorder.java index d0488be57fe21..60b79f9e2a0ac 100644 --- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaStaticRecorder.java +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/AmazonLambdaStaticRecorder.java @@ -2,15 +2,15 @@ import java.util.Set; -import com.amazonaws.services.lambda.runtime.RequestHandler; import com.amazonaws.services.lambda.runtime.RequestStreamHandler; +import io.quarkus.amazon.lambda.runtime.AmazonLambdaRecorder.RequestHandlerDefinition; import io.quarkus.runtime.annotations.Recorder; @Recorder public class AmazonLambdaStaticRecorder { - public void setHandlerClass(Class> handler) { - AmazonLambdaRecorder.initializeHandlerClass(handler); + public void setHandlerClass(RequestHandlerDefinition requestHandlerDefinition) { + AmazonLambdaRecorder.initializeHandlerClass(requestHandlerDefinition); } public void setStreamHandlerClass(Class handler) { diff --git a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/handlers/CollectionInputReader.java b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/handlers/CollectionInputReader.java index a698bcdbf400f..8a15d35bfc175 100644 --- a/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/handlers/CollectionInputReader.java +++ b/extensions/amazon-lambda/runtime/src/main/java/io/quarkus/amazon/lambda/runtime/handlers/CollectionInputReader.java @@ -2,7 +2,6 @@ import java.io.IOException; import java.io.InputStream; -import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.Collection; @@ -15,9 +14,8 @@ public class CollectionInputReader implements LambdaInputReader> { final ObjectReader reader; - public CollectionInputReader(ObjectMapper mapper, Method handler) { - Type genericParameterType = handler.getGenericParameterTypes()[0]; - JavaType constructParameterType = mapper.getTypeFactory().constructType(genericParameterType); + public CollectionInputReader(ObjectMapper mapper, Type inputElementType) { + JavaType constructParameterType = mapper.getTypeFactory().constructType(inputElementType); this.reader = mapper.readerFor(constructParameterType); }