All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.services.lambda.invoke.LambdaInvokerFactory Maven / Gradle / Ivy

/*
 * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
package com.amazonaws.services.lambda.invoke;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.amazonaws.services.lambda.AWSLambda;
import com.amazonaws.services.lambda.model.InvocationType;
import com.amazonaws.services.lambda.model.InvokeRequest;
import com.amazonaws.services.lambda.model.InvokeResult;
import com.amazonaws.services.lambda.model.LogType;
import com.amazonaws.util.Base64;
import com.amazonaws.util.BinaryUtils;
import com.amazonaws.util.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * A factory for objects that implement a user-supplied interface by invoking a remote Lambda
 * function.
 * 

* * public class Request { * // Standard POJO stuff here modeling the input your Lambda function * // expects. * } * * public class Result { * // More standard POJO stuff here modeling the output your Lambda * // function produces. * } * * public interface LambdaFunctions { * * @LambdaFunction Result doSomeStuff(Request request); } LambdaFunctions functions = * LambdaInvokerFactory.build( LambdaFunctions.class, new AWSLambdaClient()); * Request request = new Request(...); Result result = * functions.doSomeStuff(request); */ public final class LambdaInvokerFactory { private static final ObjectMapper MAPPER = new ObjectMapper(); /** * Creates a new Lambda invoker implementing the given interface and wrapping the given * {@code AWSLambda} client. * * @param interfaceClass * the interface to implement * @param awsLambda * the lambda client to use for making remote calls */ public static T build(Class interfaceClass, AWSLambda awsLambda) { return build(interfaceClass, awsLambda, new LambdaInvokerFactoryConfig()); } /** * Creates a new Lambda invoker implementing the given interface and wrapping the given * {@code AWSLambda} client. * * @param interfaceClass * the interface to implement * @param awsLambda * the lambda client to use for making remote calls * @param config * configuration for the LambdaInvokerFactory */ public static T build(Class interfaceClass, AWSLambda awsLambda, LambdaInvokerFactoryConfig config) { Object proxy = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[] { interfaceClass }, new LambdaInvocationHandler(interfaceClass, awsLambda, config)); return interfaceClass.cast(proxy); } private LambdaInvokerFactory() { } private static class LambdaInvocationHandler implements InvocationHandler { private final AWSLambda awsLambda; private final Log log; private final LambdaInvokerFactoryConfig config; public LambdaInvocationHandler(Class interfaceClass, AWSLambda awsLambda, LambdaInvokerFactoryConfig config) { this.awsLambda = awsLambda; this.log = LogFactory.getLog(interfaceClass); this.config = config; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { LambdaFunction annotation = validateInterfaceMethod(method, args); InvokeRequest invokeRequest = buildInvokeRequest(method, annotation, args == null ? null : args[0]); InvokeResult invokeResult = awsLambda.invoke(invokeRequest); return processInvokeResult(method, invokeResult); } /** * Verifies that the given method is annotated appropriately. */ private LambdaFunction validateInterfaceMethod(Method method, Object[] args) { LambdaFunction annotation = method.getAnnotation(LambdaFunction.class); if (annotation == null) { throw new LambdaSerializationException("No LambdaFunction annotation for method " + method.getName()); } if (annotation.invocationType() != InvocationType.RequestResponse && annotation.logType() != LogType.None) { throw new LambdaSerializationException("InvocationType must be RequestResponse if LogType " + "is set"); } if (args != null && args.length > 1) { throw new LambdaSerializationException("LambdaFunctions take either 0 or 1 arguments"); } return annotation; } /** * Builds an InvokeRequest from the given method, its {@code LambdaFunction} annotation, and * the input parameter (if any). */ private InvokeRequest buildInvokeRequest(Method method, LambdaFunction annotation, Object input) { InvokeRequest invokeRequest = new InvokeRequest(); String functionName = config.getLambdaFunctionNameResolver().getFunctionName(method, annotation, config); invokeRequest.setFunctionName(functionName); invokeRequest.setInvocationType(annotation.invocationType()); invokeRequest.setLogType(annotation.logType()); if (input != null) { try { String payload = MAPPER.writer().writeValueAsString(input); if (log.isDebugEnabled()) { log.debug("Serialized request object to '" + payload + "'"); } invokeRequest.setPayload(payload); } catch (JsonProcessingException ex) { throw new LambdaSerializationException("Failed to serialize request object to JSON", ex); } } return invokeRequest; } /** * Process the result of invoking a remote function. If the response includes server-side * logs, dump them into our logs; if it includes a server-side error indication, parse it * into a corresponding {@code Exception} type, otherwise parse the result payload into a * Java object suitable for returning from this method. */ private Object processInvokeResult(Method method, InvokeResult invokeResult) throws Throwable { if (invokeResult.getLogResult() != null && log.isInfoEnabled()) { try { String decoded = new String(Base64.decode(invokeResult.getLogResult()), StringUtils.UTF8); log.info(method.getName() + " log:\n\t" + decoded.replaceAll("\n", "\n\t")); } catch (Exception ex) { log.warn("Error decoding log result '" + invokeResult.getLogResult() + "'", ex); } } String functionError = invokeResult.getFunctionError(); if (functionError == null) { // Success. return getObjectFromPayload(method, invokeResult); } else { throw getExceptionFromPayload(method, invokeResult); } } /** * Reads a Java object suitable for returning from the given method from the payload of the * given {@code InvokeResult} (or returns {@code null} if the method has no return value or * the response contains no payload). * * @throws LambdaSerializationException * on error deserializing */ private Object getObjectFromPayload(Method method, InvokeResult invokeResult) { try { return getObjectFromPayload(method.getGenericReturnType(), invokeResult.getPayload()); } catch (IOException ex) { throw new LambdaSerializationException("Failed to parse Lambda function result", ex); } } private Throwable getExceptionFromPayload(Method method, InvokeResult invokeResult) { Throwable throwable = null; String message = "Unexpected error executing Lambda function"; String type = null; List stackTrace = null; try { LambdaFunctionError error = getObjectFromPayload(LambdaFunctionError.class, invokeResult.getPayload()); if (error != null) { message = error.getErrorMessage(); type = error.getErrorType(); stackTrace = error.getStackTrace(); throwable = getCustomException(method, error); } } catch (Exception ex) { log.warn("Error parsing exception information from response " + "payload", ex); } if (throwable == null) { throwable = new LambdaFunctionException(message, "Handled".equals(invokeResult.getFunctionError()), type); } if (stackTrace != null) { fillStackTrace(throwable, stackTrace, method.getDeclaringClass()); } return throwable; } private Throwable getCustomException(Method method, LambdaFunctionError error) { String type = error.getErrorType(); Constructor constructor = null; if (type != null) { for (Class exceptionType : method.getExceptionTypes()) { if (exceptionType.getSimpleName().startsWith(type)) { constructor = findConstructor(exceptionType); if (constructor != null) { break; } } } } if (constructor != null) { try { return (Throwable) constructor.newInstance(error.getErrorMessage()); } catch (Exception ex) { log.warn("Error constructing custom exception", ex); } } return null; } private Constructor findConstructor(Class type) { for (Constructor constructor : type.getConstructors()) { Class[] params = constructor.getParameterTypes(); if (params != null && params.length == 1 && String.class.equals(params[0])) { return constructor; } } return null; } private void fillStackTrace(Throwable throwable, List stackTrace, Class interfaceClass) { StackTraceElement[] elements = new StackTraceElement[stackTrace.size()]; for (int i = 0; i < stackTrace.size(); ++i) { elements[i] = new StackTraceElement(interfaceClass.getName(), stackTrace.get(i).trim(), null, 0); } throwable.setStackTrace(elements); } private T getObjectFromPayload(Class type, ByteBuffer payload) throws IOException { return type.cast(getObjectFromPayload((Type) type, payload)); } private Object getObjectFromPayload(Type type, ByteBuffer payload) throws IOException { if (type == void.class || payload.remaining() == 0) { return null; } JavaType javaType = MAPPER.getTypeFactory().constructType(type); return MAPPER.reader(javaType).readValue(BinaryUtils.copyAllBytesFrom(payload)); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy