package com.serverless;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
public class InvokeBridge {
private File artifact;
private String className;
private String handlerName;
private Object instance;
private Class clazz;
private InvokeBridge() {
this.artifact = new File(new File("."), System.getProperty("artifactPath"));
this.className = System.getProperty("className");
this.handlerName = System.getProperty("handlerName");
try {
HashMap<String, Object> parsedInput = parseInput(getInput());
HashMap<String, Object> eventMap = (HashMap<String, Object>) parsedInput.get("event");
this.instance = this.getInstance();
Object output = this.invoke(eventMap, this.getContext(parsedInput));
String string = null;
if(output != null) {
if(output.getClass().isAssignableFrom(ByteArrayOutputStream.class)){
string = new String(((ByteArrayOutputStream)output).toByteArray());
}else {
string = output.toString();
}
}
System.out.println(string);
} catch (Exception e) {
e.printStackTrace();
}
}
private Context getContext(HashMap<String, Object> parsedInput) {
HashMap<String, Object> contextMap = (HashMap<String, Object>) parsedInput.get("context");
String name = (String) contextMap.getOrDefault("name", "functionName");
String version = (String) contextMap.getOrDefault("version", "LATEST");
String logGroupName = (String) contextMap.getOrDefault("logGroupName", "logGroup");
int timeout = Integer.parseInt(String.valueOf(contextMap.getOrDefault("timeout", 5)));
return new Context(name, version, logGroupName, timeout);
}
private Object getInstance() throws Exception {
URL[] urls = {this.artifact.toURI().toURL()};
URLClassLoader child = new URLClassLoader(urls, this.getClass().getClassLoader());
this.clazz = Class.forName(this.className, true, child);
return this.clazz.newInstance();
}
private Object invoke(HashMap<String, Object> event, Context context) throws Exception {
Method method = findHandlerMethod(this.clazz, this.handlerName);
Class requestClass = method.getParameterTypes()[0];
Object request = event;
if (!requestClass.isAssignableFrom(event.getClass())
&& !requestClass.isAssignableFrom(InputStream.class)) {
request = requestClass.newInstance();
PropertyDescriptor[] properties = Introspector.getBeanInfo(requestClass).getPropertyDescriptors();
for(int i=0; i < properties.length; i++) {
if (properties[i].getWriteMethod() == null) continue;
String propertyName = properties[i].getName();
if (event.containsKey(propertyName)) {
properties[i].getWriteMethod().invoke(request, event.get(propertyName));
}
}
}
if (method.getParameterCount() == 1) {
return method.invoke(this.instance, request);
} else if (method.getParameterCount() == 2) {
return method.invoke(this.instance, request, context);
} else if (method.getParameterCount() == 3 && requestClass.isAssignableFrom(InputStream.class)) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteArrayInputStream inputStream = new ByteArrayInputStream(new ObjectMapper().writeValueAsBytes(event));
method.invoke(this.instance, inputStream, outputStream, context);
return outputStream;
} else {
throw new NoSuchMethodException("Handler should take 1, 2, or 3 (com.amazonaws.services.lambda.runtime.RequestStreamHandler compatible handlers) arguments: " + method);
}
}
private Method findHandlerMethod(Class clazz, String handlerName) throws Exception {
Method candidateMethod = null;
for(Method method: clazz.getMethods()) {
if (method.getName().equals(handlerName) && !method.isBridge()) {
// Select the method with the largest number of parameters
// If two or more methods have the same number of parameters, AWS Lambda selects the method that has
// the Context as the last parameter.
// If none or all of these methods have the Context parameter, then the behavior is undefined.
int paramCount = method.getParameterCount();
boolean lastParamIsContext = paramCount >= 1 && method.getParameterTypes()[paramCount-1].getName().equals("com.amazonaws.services.lambda.runtime.Context");
if (candidateMethod == null || paramCount > candidateMethod.getParameterCount() || (paramCount == candidateMethod.getParameterCount() && lastParamIsContext)) {
candidateMethod = method;
}
}
}
if (candidateMethod == null) {
throw new NoSuchMethodException("Could not find handler for " + handlerName + " in " + clazz.getName());
}
return candidateMethod;
}
private HashMap<String, Object> parseInput(String input) throws IOException {
TypeReference<HashMap<String,Object>> typeRef = new TypeReference<HashMap<String,Object>>() {};
ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
JsonNode jsonNode = mapper.readTree(input);
return mapper.convertValue(jsonNode, typeRef);
}
private String getInput() throws IOException {
BufferedReader streamReader = new BufferedReader(new InputStreamReader(System.in, "UTF-8"));
StringBuilder inputStringBuilder = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null) {
inputStringBuilder.append(inputStr);
}
return inputStringBuilder.toString();
}
public static void main(String[] args) {
new InvokeBridge();
}
}