package com.amazonaws.mobileconnectors.lambdainvoker;
import android.util.Log;
import com.amazonaws.mobileconnectors.util.ClientContext;
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.StringUtils;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.nio.ByteBuffer;
/**
* Invocation handler for Lambda.
*/
class LambdaInvocationHandler implements InvocationHandler {
private static final String TAG = "LambdaInvocationHandler";
// -------------------------------------------------------------
// Variables - Private
// -------------------------------------------------------------
private final AWSLambda lambda;
private final LambdaDataBinder binder;
private final ClientContext clientContext;
// -------------------------------------------------------------
// Constructors
// -------------------------------------------------------------
/**
* @param lambda lambda client
* @param binder data binder
* @param clientContext client context, null if unavailable
*/
public LambdaInvocationHandler(AWSLambda lambda, LambdaDataBinder binder,
ClientContext clientContext) {
this.lambda = lambda;
this.binder = binder;
this.clientContext = clientContext;
}
// -------------------------------------------------------------
// Implementation - InvocationHandler
// -------------------------------------------------------------
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
validateInterfaceMethod(method, args);
// With Android version before Lollipop (API level 22), args can be an
// empty array when the method takes no arguments.
final Object buildArg = (args == null || args.length == 0) ? null : args[0];
InvokeRequest invokeRequest = buildInvokeRequest(method, buildArg);
InvokeResult invokeResult = lambda.invoke(invokeRequest);
return processInvokeResult(method, invokeResult);
}
// -------------------------------------------------------------
// Methods - Private
// -------------------------------------------------------------
void validateInterfaceMethod(Method method, Object[] args) {
if (method.getAnnotation(LambdaFunction.class) == null) {
throw new UnsupportedOperationException("No LambdaFunction annotation for method "
+ method.getName());
}
if (args != null && args.length > 1) {
throw new UnsupportedOperationException(
"LambdaFunctions take either 0 or 1 arguments.");
}
}
InvokeRequest buildInvokeRequest(Method method, Object object) throws IOException {
LambdaFunction lambdaFunction = method.getAnnotation(LambdaFunction.class);
InvokeRequest invokeRequest = new InvokeRequest();
if (lambdaFunction.functionName().isEmpty()) {
invokeRequest.setFunctionName(method.getName());
} else {
invokeRequest.setFunctionName(lambdaFunction.functionName());
}
invokeRequest.setLogType(lambdaFunction.logType());
// If the log type is other than 'None', force to be RequestResponse.
if (!LogType.None.equals(lambdaFunction.logType())) {
invokeRequest.setInvocationType(InvocationType.RequestResponse);
} else {
invokeRequest.setInvocationType(lambdaFunction.invocationType());
}
if (!lambdaFunction.qualifier().isEmpty()) {
invokeRequest.setQualifier(lambdaFunction.qualifier());
}
// set base64 encoded client context string
if (clientContext != null) {
invokeRequest.setClientContext(clientContext.toBase64String());
}
invokeRequest.setPayload(ByteBuffer.wrap(binder.serialize(object)));
return invokeRequest;
}
Object processInvokeResult(Method method, InvokeResult invokeResult)
throws IOException {
if (invokeResult.getLogResult() != null) {
Log.d(TAG, method.getName() + " log: "
+ new String(Base64.decode(invokeResult.getLogResult()), StringUtils.UTF8));
}
if (invokeResult.getFunctionError() != null) {
throw new LambdaFunctionException(invokeResult.getFunctionError(),
new String(invokeResult.getPayload().array(), StringUtils.UTF8));
}
// deserialize payload
if (invokeResult.getStatusCode() == HttpURLConnection.HTTP_NO_CONTENT
|| method.getReturnType().equals(void.class)) {
return null;
}
return binder.deserialize(invokeResult.getPayload().array(),
method.getReturnType());
}
}