diff --git a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaBuildItem.java b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaBuildItem.java index d5f10e3e0ab8d..85b68675ab33b 100644 --- a/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaBuildItem.java +++ b/extensions/amazon-lambda/deployment/src/main/java/io/quarkus/amazon/lambda/deployment/AmazonLambdaBuildItem.java @@ -6,10 +6,12 @@ public final class AmazonLambdaBuildItem extends MultiBuildItem { private final String handlerClass; private final String name; + private final boolean streamHandler; - public AmazonLambdaBuildItem(String handlerClass, String name) { + public AmazonLambdaBuildItem(String handlerClass, String name, boolean streamHandler) { this.handlerClass = handlerClass; this.name = name; + this.streamHandler = streamHandler; } public String getHandlerClass() { @@ -19,4 +21,8 @@ public String getHandlerClass() { public String getName() { return name; } + + public boolean isStreamHandler() { + return streamHandler; + } } 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 2e94200e3935d..4f28c8eceb664 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 @@ -19,6 +19,7 @@ import org.joda.time.DateTime; import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; import io.quarkus.amazon.lambda.runtime.AmazonLambdaRecorder; import io.quarkus.amazon.lambda.runtime.FunctionError; @@ -49,6 +50,7 @@ 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 NAMED = DotName.createSimple(Named.class.getName()); private static final Logger log = Logger.getLogger(AmazonLambdaProcessor.class); @@ -70,6 +72,10 @@ List discover(CombinedIndexBuildItem combinedIndexBuildIt BuildProducer reflectiveHierarchy, BuildProducer reflectiveClassBuildItemBuildProducer) throws BuildException { Collection allKnownImplementors = combinedIndexBuildItem.getIndex().getAllKnownImplementors(REQUEST_HANDLER); + Collection allKnownStreamImplementors = combinedIndexBuildItem.getIndex() + .getAllKnownImplementors(REQUEST_STREAM_HANDLER); + + allKnownImplementors.addAll(allKnownStreamImplementors); if (allKnownImplementors.size() > 0 && providedLambda.isPresent()) { throw new BuildException( "Multiple handler classes. You have a custom handler class and the " + providedLambda.get().getProvider() @@ -90,24 +96,30 @@ List discover(CombinedIndexBuildItem combinedIndexBuildIt } final String lambda = name.toString(); - ret.add(new AmazonLambdaBuildItem(lambda, cdiName)); reflectiveClassBuildItemBuildProducer.produce(new ReflectiveClassBuildItem(true, false, lambda)); ClassInfo current = info; boolean done = false; + boolean streamHandler = false; while (current != null && !done) { for (MethodInfo method : current.methods()) { - if (method.name().equals("handleRequest") - && method.parameters().size() == 2 - && !method.parameters().get(0).name().equals(DotName.createSimple(Object.class.getName()))) { - reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(method.parameters().get(0))); - reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(method.returnType())); - done = true; - break; + if (method.name().equals("handleRequest")) { + if (method.parameters().size() == 3) { + streamHandler = true; + done = true; + break; + } else if (method.parameters().size() == 2 + && !method.parameters().get(0).name().equals(DotName.createSimple(Object.class.getName()))) { + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(method.parameters().get(0))); + reflectiveHierarchy.produce(new ReflectiveHierarchyBuildItem(method.returnType())); + done = true; + break; + } } } current = combinedIndexBuildItem.getIndex().getClassByName(current.superName()); } + ret.add(new AmazonLambdaBuildItem(lambda, cdiName, streamHandler)); } additionalBeanBuildItemBuildProducer.produce(builder.build()); reflectiveClassBuildItemBuildProducer @@ -159,21 +171,50 @@ public void recordHandlerClass(List lambdas, List orderServicesFirst, // try to order this after service recorders RecorderContext context) { if (providedLambda.isPresent()) { - Class> handlerClass = (Class>) context - .classProxy(providedLambda.get().getHandlerClass().getName()); - recorder.setHandlerClass(handlerClass, beanContainerBuildItem.getValue()); + boolean useStreamHandler = false; + for (Class handleInterface : providedLambda.get().getHandlerClass().getInterfaces()) { + if (handleInterface.getName().equals(RequestStreamHandler.class.getName())) { + useStreamHandler = true; + } + } + if (useStreamHandler) { + Class handlerClass = (Class) context + .classProxy(providedLambda.get().getHandlerClass().getName()); + recorder.setStreamHandlerClass(handlerClass, beanContainerBuildItem.getValue()); + } else { + Class> handlerClass = (Class>) context + .classProxy(providedLambda.get().getHandlerClass().getName()); + + recorder.setHandlerClass(handlerClass, beanContainerBuildItem.getValue()); + } } else if (lambdas != null) { List>> unnamed = new ArrayList<>(); Map>> named = new HashMap<>(); + + List> unnamedStreamHandler = new ArrayList<>(); + Map> namedStreamHandler = new HashMap<>(); + for (AmazonLambdaBuildItem i : lambdas) { - if (i.getName() == null) { - unnamed.add((Class>) context.classProxy(i.getHandlerClass())); + if (i.isStreamHandler()) { + if (i.getName() == null) { + unnamedStreamHandler + .add((Class) context.classProxy(i.getHandlerClass())); + } else { + namedStreamHandler.put(i.getName(), + (Class) context.classProxy(i.getHandlerClass())); + } } else { - named.put(i.getName(), (Class>) context.classProxy(i.getHandlerClass())); + if (i.getName() == null) { + unnamed.add((Class>) context.classProxy(i.getHandlerClass())); + } else { + named.put(i.getName(), (Class>) context.classProxy(i.getHandlerClass())); + } } } - recorder.chooseHandlerClass(unnamed, named, beanContainerBuildItem.getValue(), config); + + recorder.chooseHandlerClass(unnamed, named, unnamedStreamHandler, namedStreamHandler, + beanContainerBuildItem.getValue(), config); } } diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/manage.sh b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/manage.sh index 173f00b0fda86..b2796506ffa13 100644 --- a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/manage.sh +++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/manage.sh @@ -9,7 +9,11 @@ function cmd_create() { --handler ${HANDLER} \ --runtime ${RUNTIME} \ --role ${LAMBDA_ROLE_ARN} \ + --timeout 15 \ + --memory-size 256 \ ${LAMBDA_META} +# Enable and move this param above ${LAMBDA_META}, if using AWS X-Ray +# --tracing-config Mode=Active \ } function cmd_delete() { @@ -27,6 +31,8 @@ function cmd_invoke() { --log-type Tail \ --query 'LogResult' \ --output text | base64 -d + { set +x; } 2>/dev/null + cat response.txt && rm -f response.txt } function cmd_update() { diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml index dc90fd5957a93..01d72158967d8 100644 --- a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml +++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/pom.xml @@ -32,6 +32,17 @@ io.quarkus quarkus-amazon-lambda + + io.quarkus + quarkus-test-amazon-lambda + ${quarkus.version} + test + + + io.quarkus + quarkus-junit5 + test + diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambda.java b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambda.java new file mode 100644 index 0000000000000..e15cdd40b7f85 --- /dev/null +++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/main/java/StreamLambda.java @@ -0,0 +1,25 @@ +package ${package}; + +import javax.inject.Inject; +import javax.inject.Named; + +import com.amazonaws.services.lambda.runtime.Context; +import com.amazonaws.services.lambda.runtime.RequestStreamHandler; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + + +@Named("stream") +public class StreamLambda implements RequestStreamHandler { + + @Override + public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { + int letter; + while ((letter = inputStream.read()) != -1) { + int character = Character.toUpperCase(letter); + outputStream.write(character); + } + } +} diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties index 984150ad856c8..b9dae3d8d90c7 100644 --- a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties +++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/main/resources/application.properties @@ -1 +1,3 @@ quarkus.lambda.handler=test + +quarkus.lambda.enable-polling-jvm-mode=true diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/java/LambdaHandlerTest.java b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/java/LambdaHandlerTest.java new file mode 100644 index 0000000000000..4f89d941f2b53 --- /dev/null +++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/java/LambdaHandlerTest.java @@ -0,0 +1,22 @@ +package ${package}; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.amazon.lambda.test.LambdaClient; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class LambdaHandlerTest { + + @Test + public void testSimpleLambdaSuccess() throws Exception { + InputObject in = new InputObject(); + in.setGreeting("Hello"); + in.setName("Stu"); + OutputObject out = LambdaClient.invoke(OutputObject.class, in); + Assertions.assertEquals("Hello Stu", out.getResult()); + Assertions.assertTrue(out.getRequestId().matches("aws-request-\\d"), "Expected requestId as 'aws-request-'"); + } + +} diff --git a/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties new file mode 100644 index 0000000000000..7c37ee7b060a8 --- /dev/null +++ b/extensions/amazon-lambda/maven-archetype/src/main/resources/archetype-resources/src/test/resources/application.properties @@ -0,0 +1,5 @@ +quarkus.lambda.handler=test + +quarkus.lambda.enable-polling-jvm-mode=true + + 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 d21c13c854d06..677b021d0fff0 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 @@ -12,10 +12,7 @@ import org.jboss.logging.Logger; -import com.amazonaws.services.lambda.runtime.ClientContext; -import com.amazonaws.services.lambda.runtime.CognitoIdentity; -import com.amazonaws.services.lambda.runtime.Context; -import com.amazonaws.services.lambda.runtime.RequestHandler; +import com.amazonaws.services.lambda.runtime.*; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; @@ -40,11 +37,23 @@ public class AmazonLambdaRecorder { private static final Logger log = Logger.getLogger(AmazonLambdaRecorder.class); private static Class> handlerClass; + private static Class streamHandlerClass; private static BeanContainer beanContainer; private static ObjectMapper objectMapper = new ObjectMapper(); private static ObjectReader objectReader; private static ObjectWriter objectWriter; + public void setStreamHandlerClass(Class handler, BeanContainer container) { + streamHandlerClass = handler; + beanContainer = container; + AmazonLambdaRecorder.objectMapper = getObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) + .registerModule(new JodaModule()); + + Method handlerMethod = discoverStreamHandlerMethod(streamHandlerClass); + } + public void setHandlerClass(Class> handler, BeanContainer container) { handlerClass = handler; beanContainer = container; @@ -52,6 +61,7 @@ public void setHandlerClass(Class> handler, BeanC .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true) .registerModule(new JodaModule()); + Method handlerMethod = discoverHandlerMethod(handlerClass); objectReader = objectMapper.readerFor(handlerMethod.getParameterTypes()[0]); objectWriter = objectMapper.writerFor(handlerMethod.getReturnType()); @@ -74,10 +84,32 @@ private ObjectMapper getObjectMapper() { * @throws IOException */ public static void handle(InputStream inputStream, OutputStream outputStream, Context context) throws IOException { - Object request = objectReader.readValue(inputStream); - RequestHandler handler = beanContainer.instance(handlerClass); - Object response = handler.handleRequest(request, context); - objectWriter.writeValue(outputStream, response); + if (streamHandlerClass != null) { + RequestStreamHandler handler = beanContainer.instance(streamHandlerClass); + handler.handleRequest(inputStream, outputStream, context); + } else { + Object request = objectReader.readValue(inputStream); + RequestHandler handler = beanContainer.instance(handlerClass); + Object response = handler.handleRequest(request, context); + objectWriter.writeValue(outputStream, response); + } + } + + private Method discoverStreamHandlerMethod(Class handlerClass) { + final Method[] methods = handlerClass.getMethods(); + Method method = null; + for (int i = 0; i < methods.length && method == null; i++) { + if (methods[i].getName().equals("handleRequest")) { + final Class[] types = methods[i].getParameterTypes(); + if (types.length == 3 && !types[0].equals(Object.class)) { + method = methods[i]; + } + } + } + if (method == null) { + method = methods[0]; + } + return method; } private Method discoverHandlerMethod(Class> handlerClass) { @@ -99,40 +131,57 @@ private Method discoverHandlerMethod(Class> handl public void chooseHandlerClass(List>> unamedHandlerClasses, Map>> namedHandlerClasses, + List> unamedStreamHandlerClasses, + Map> namedStreamHandlerClasses, BeanContainer container, LambdaConfig config) { - Class> handlerClass; + + Class> handlerClass = null; + Class handlerStreamClass = null; if (config.handler.isPresent()) { handlerClass = namedHandlerClasses.get(config.handler.get()); - if (handlerClass == null) { + handlerStreamClass = namedStreamHandlerClasses.get(config.handler.get()); + + if (handlerClass == null && handlerStreamClass == null) { String errorMessage = "Unable to find handler class with name " + config.handler.get() + " make sure there is a handler class in the deployment with the correct @Named annotation"; throw new RuntimeException(errorMessage); } } else { - if (unamedHandlerClasses.size() > 1 || namedHandlerClasses.size() > 1 - || (unamedHandlerClasses.size() > 0 && namedHandlerClasses.size() > 0)) { + int unnamedTotal = unamedHandlerClasses.size() + unamedStreamHandlerClasses.size(); + int namedTotal = namedHandlerClasses.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() + " implementation in the deployment"; + + RequestHandler.class.getName() + " or, " + RequestStreamHandler.class.getName() + + " implementation in the deployment"; throw new RuntimeException(errorMessage); - } - if (unamedHandlerClasses.isEmpty() && namedHandlerClasses.isEmpty()) { - String errorMessage = "Unable to find handler class, make sure your deployment includes a " - + RequestHandler.class.getName() + " implementation"; + } 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); + } else if ((unnamedTotal + namedTotal) == 1) { + if (!unamedHandlerClasses.isEmpty()) { + handlerClass = unamedHandlerClasses.get(0); + } else if (!namedHandlerClasses.isEmpty()) { + handlerClass = namedHandlerClasses.values().iterator().next(); + } else if (!unamedStreamHandlerClasses.isEmpty()) { + handlerStreamClass = unamedStreamHandlerClasses.get(0); + } else if (!namedStreamHandlerClasses.isEmpty()) { + handlerStreamClass = namedStreamHandlerClasses.values().iterator().next(); + } } - if (!unamedHandlerClasses.isEmpty()) { - handlerClass = unamedHandlerClasses.get(0); - } else { - handlerClass = namedHandlerClasses.values().iterator().next(); - } } - setHandlerClass(handlerClass, container); + + if (handlerStreamClass != null) { + setStreamHandlerClass(handlerStreamClass, container); + } else { + setHandlerClass(handlerClass, container); + } } @SuppressWarnings("rawtypes") public void startPollLoop(ShutdownContext context) { - AtomicBoolean running = new AtomicBoolean(true); ObjectReader cognitoIdReader = objectMapper.readerFor(CognitoIdentity.class); @@ -155,9 +204,10 @@ public void run() { while (running.get()) { HttpURLConnection requestConnection = (HttpURLConnection) requestUrl.openConnection(); + HttpURLConnection responseStreamConnection = null; try { String requestId = requestConnection.getHeaderField(AmazonLambdaApi.LAMBDA_RUNTIME_AWS_REQUEST_ID); - Object response; + Object response = null; try { // todo custom runtime requires the setting of an ENV Var // Should we override getenv? If we do that, @@ -165,19 +215,32 @@ public void run() { String traceId = requestConnection.getHeaderField(AmazonLambdaApi.LAMBDA_TRACE_HEADER_KEY); TraceId.setTraceId(traceId); - Object val = objectReader.readValue(requestConnection.getInputStream()); - RequestHandler handler = beanContainer.instance(handlerClass); - response = handler.handleRequest(val, - new AmazonLambdaContext(requestConnection, cognitoIdReader, clientCtxReader)); + if (streamHandlerClass != null) { + RequestStreamHandler handler = beanContainer.instance(streamHandlerClass); + responseStreamConnection = responseStream(AmazonLambdaApi.invocationResponse(requestId)); + handler.handleRequest(requestConnection.getInputStream(), + responseStreamConnection.getOutputStream(), + new AmazonLambdaContext(requestConnection, cognitoIdReader, clientCtxReader)); + while (responseStreamConnection.getInputStream().read() != -1) { + // Read data + } + } else { + Object val = objectReader.readValue(requestConnection.getInputStream()); + RequestHandler handler = beanContainer.instance(handlerClass); + response = handler.handleRequest(val, + new AmazonLambdaContext(requestConnection, cognitoIdReader, clientCtxReader)); + } } catch (Exception e) { log.error("Failed to run lambda", e); - postResponse(AmazonLambdaApi.invocationError(requestId), new FunctionError(e.getClass().getName(), e.getMessage()), objectMapper); + continue; } - postResponse(AmazonLambdaApi.invocationResponse(requestId), response, objectMapper); + if (streamHandlerClass == null) { + postResponse(AmazonLambdaApi.invocationResponse(requestId), response, objectMapper); + } } catch (Exception e) { log.error("Error running lambda", e); Application app = Application.currentApplication(); @@ -186,11 +249,12 @@ public void run() { } return; } finally { + if (responseStreamConnection != null) { + responseStreamConnection.getInputStream().close(); + } requestConnection.getInputStream().close(); } - } - } catch (Exception e) { try { log.error("Lambda init error", e); @@ -221,6 +285,13 @@ private void checkQuarkusBootstrapped() { Application.currentApplication().start(args); } + private HttpURLConnection responseStream(URL url) throws IOException { + HttpURLConnection responseConnection = (HttpURLConnection) url.openConnection(); + responseConnection.setDoOutput(true); + responseConnection.setRequestMethod("POST"); + return responseConnection; + } + private void postResponse(URL url, Object response, ObjectMapper mapper) throws IOException { HttpURLConnection responseConnection = (HttpURLConnection) url.openConnection(); responseConnection.setDoOutput(true); diff --git a/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaClient.java b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaClient.java index 366f397080184..37439a471680c 100644 --- a/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaClient.java +++ b/test-framework/amazon-lambda/src/main/java/io/quarkus/amazon/lambda/test/LambdaClient.java @@ -1,5 +1,7 @@ package io.quarkus.amazon.lambda.test; +import java.io.InputStream; +import java.io.OutputStream; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; @@ -45,6 +47,50 @@ public class LambdaClient { } + public static void invoke(InputStream inputStream, OutputStream outputStream) { + if (problem != null) { + throw new RuntimeException(problem); + } + try { + String id = "aws-request-" + REQUEST_ID_GENERATOR.incrementAndGet(); + CompletableFuture result = new CompletableFuture<>(); + REQUESTS.put(id, result); + StringBuilder requestBody = new StringBuilder(); + int i = 0; + while ((i = inputStream.read()) != -1) { + requestBody.append((char) i); + } + REQUEST_QUEUE.add(new Map.Entry() { + + @Override + public String getKey() { + return id; + } + + @Override + public String getValue() { + return requestBody.toString(); + } + + @Override + public String setValue(String value) { + return null; + } + }); + String output = result.get(); + outputStream.write(output.getBytes()); + } catch (Exception e) { + if (e instanceof ExecutionException) { + Throwable ex = e.getCause(); + if (ex instanceof RuntimeException) { + throw (RuntimeException) ex; + } + throw new RuntimeException(ex); + } + throw new RuntimeException(e); + } + } + public static T invoke(Class returnType, Object input) { if (problem != null) { throw new RuntimeException(problem);