All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
cloud.localstack.LambdaExecutor Maven / Gradle / Ivy
package cloud.localstack;
import cloud.localstack.awssdkv1.lambda.DDBEventParser;
import cloud.localstack.awssdkv1.lambda.KinesisEventParser;
import cloud.localstack.awssdkv1.lambda.S3EventParser;
import cloud.localstack.lambda_handler.HandlerNameParseResult;
import cloud.localstack.lambda_handler.MultipleMatchingHandlersException;
import cloud.localstack.lambda_handler.NoMatchingHandlerException;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
import com.amazonaws.services.lambda.runtime.events.SNSEvent;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
import com.amazonaws.util.StringInputStream;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Simple implementation of a Java Lambda function executor.
*
* @author Waldemar Hummer
*/
@SuppressWarnings("restriction")
public class LambdaExecutor {
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception {
if (args.length != 1 && args.length != 2) {
System.err.println("Usage: java " + LambdaExecutor.class.getSimpleName() +
" [] ");
System.exit(1);
}
String fileContent = args.length == 1 ? readFile(args[0]) : readFile(args[1]);
ObjectMapper reader = new ObjectMapper();
reader.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
reader.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Map map = reader.readerFor(Map.class).readValue(fileContent);
List> records = (List>) get(map, "Records");
Object inputObject = map;
String handlerName;
if (args.length == 2) {
handlerName = args[0];
} else {
String handlerEnvVar = System.getenv("_HANDLER");
if (handlerEnvVar == null) {
System.err.println("Handler must be provided by '_HANDLER' environment variable");
System.exit(1);
}
handlerName = handlerEnvVar;
}
HandlerNameParseResult parseResult = parseHandlerName(handlerName);
Object handler = getHandler(parseResult.getClassName());
String handlerMethodName = parseResult.getHandlerMethod();
Method handlerMethod = handlerMethodName != null ? getHandlerMethodByName(handler, handlerMethodName) : null;
if (records == null) {
Optional deserializedInput = getInputObject(reader, fileContent, handler, handlerMethod);
if (deserializedInput.isPresent()) {
inputObject = deserializedInput.get();
}
} else {
if (records.stream().anyMatch(record -> record.containsKey("kinesis") || record.containsKey("Kinesis"))) {
inputObject = KinesisEventParser.parse(records);
} else if (records.stream().anyMatch(record -> record.containsKey("Sns"))) {
SNSEvent snsEvent = new SNSEvent();
inputObject = snsEvent;
snsEvent.setRecords(new LinkedList<>());
for (Map record : records) {
SNSEvent.SNSRecord r = new SNSEvent.SNSRecord();
snsEvent.getRecords().add(r);
SNSEvent.SNS snsRecord = new SNSEvent.SNS();
Map sns = (Map) get(record, "Sns");
snsRecord.setMessage((String) get(sns, "Message"));
snsRecord.setMessageAttributes((Map) get(sns, "MessageAttributes"));
snsRecord.setType("Notification");
snsRecord.setTimestamp(new DateTime());
r.setSns(snsRecord);
}
} else if (records.stream().anyMatch(record -> record.containsKey("dynamodb"))) {
inputObject = DDBEventParser.parse(records);
} else if (records.stream().anyMatch(record -> record.containsKey("s3"))) {
inputObject = S3EventParser.parse(records);
} else if (records.stream().anyMatch(record -> Objects.equals(record.get("eventSource"), "aws:sqs"))) {
inputObject = reader.readValue(fileContent, SQSEvent.class);
}
}
Context ctx = new LambdaContext(UUID.randomUUID().toString());
if (handlerMethod != null || handler instanceof RequestHandler) {
Object result;
if (handlerMethod != null) {
// use reflection to load handler method from class
result = handlerMethod.invoke(handler, inputObject, ctx);
} else {
result = ((RequestHandler) handler).handleRequest(inputObject, ctx);
}
try {
result = new ObjectMapper().writeValueAsString(result);
} catch (JsonProcessingException jsonException) {
// continue with results as it is
}
// The contract with lambci is to print the result to stdout, whereas logs go to stderr
System.out.println(result);
} else if (handler instanceof RequestStreamHandler) {
OutputStream os = new ByteArrayOutputStream();
((RequestStreamHandler) handler).handleRequest(
new StringInputStream(fileContent), os, ctx);
System.out.println(os);
}
}
/**
* Returns the method matching the specified name implemented in the given handler object class
* @param handler Handler the method in question belongs to
* @param handlerMethodName Name of the method we are looking for in the handler
* @return Method object for the method with the given method name
* @throws MultipleMatchingHandlersException Thrown when multiple methods in the given handler exist for the given name
* @throws NoMatchingHandlerException Thrown if no method in the handler is matching the given name
*/
private static Method getHandlerMethodByName(Object handler, String handlerMethodName) throws MultipleMatchingHandlersException, NoMatchingHandlerException {
List handlerMethods = Arrays.stream(handler.getClass().getMethods())
.filter(method -> method.getName().equals(handlerMethodName) && !method.isBridge()) // we do not want bridge methods here
.collect(Collectors.toList());
if (handlerMethods.size() > 1) {
throw new MultipleMatchingHandlersException("Multiple matching handlers: " + handlerMethods);
} else if (handlerMethods.isEmpty()) {
throw new NoMatchingHandlerException("No matching handlers for method name: "
+ handlerMethodName);
}
return handlerMethods.get(0);
}
/**
* Getting the input object for the handler function.
* @param mapper ObjectMapper that maps the objectString into the target parameter type
* @param objectString Object we got from the lambda invocation
* @param handler Handler object we need to get the correct input type
* @param handlerMethod Handler method we need to get the correct input type
* @return Optional of the input object for the lambda handler
*/
private static Optional getInputObject(ObjectMapper mapper, String objectString, Object handler, Method handlerMethod) {
Optional inputObject = Optional.empty();
try {
if (handlerMethod != null) {
Class> handlerInputType = Class.forName(handlerMethod.getParameterTypes()[0].getName());
inputObject = Optional.of(mapper.readerFor(handlerInputType).readValue(objectString));
} else {
Optional handlerInterface = Arrays.stream(handler.getClass().getGenericInterfaces())
.filter(genericInterface ->
((ParameterizedType) genericInterface).getRawType().equals(RequestHandler.class))
.findFirst();
if (handlerInterface.isPresent()) {
Class> handlerInputType = Class.forName(((ParameterizedType) handlerInterface.get())
.getActualTypeArguments()[0].getTypeName());
inputObject = Optional.of(mapper.readerFor(handlerInputType).readValue(objectString));
}
}
} catch (Exception genericException) {
// do nothing
}
return inputObject;
}
/**
* Parses the handler name
* Depending on the string, the result handlerMethod can be null
* @param handlerName Handler name in the format "java.package.class::handlerMethodName" or "java.package.class"
* @return Result containing the class name, and the handler method if specified
*/
private static HandlerNameParseResult parseHandlerName(String handlerName) {
String[] split = handlerName.split("::", 2);
String className = split[0];
String handlerMethod = split.length > 1 ? split[1] : null;
return new HandlerNameParseResult(className, handlerMethod);
}
/**
* Returns a instance of the class specified by handler name
* @param handlerName name (including package information) of the class to load and instantiate
* @return New object of handlerName class
*/
private static Object getHandler(String handlerName) throws NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException, ClassNotFoundException {
Class> clazz = Class.forName(handlerName);
return clazz.getConstructor().newInstance();
}
public static T get(Map map, String key) {
T result = map.get(key);
if (result != null) {
return result;
}
key = StringUtils.uncapitalize(key);
result = map.get(key);
if (result != null) {
return result;
}
return map.get(key.toLowerCase());
}
public static String readFile(String file) throws Exception {
if (!file.startsWith("/")) {
file = System.getProperty("user.dir") + "/" + file;
}
return Files.lines(Paths.get(file), StandardCharsets.UTF_8).collect(Collectors.joining());
}
}