Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@
- [kotlin-simple](templates/kotlin-simple)
- [kotlin-spark-monolith](templates/kotlin-spark-monolith)
- [scala-simple](templates/scala-simple)

### Fork modifications

- Quarkus request handler execution
- Changes from https://github.com/bytekast/serverless-toolkit/pull/1
2 changes: 1 addition & 1 deletion libs/java-invoke-local/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ repositories {

dependencies {
implementation 'info.picocli:picocli:4.1.1'
implementation 'org.codehaus.groovy:groovy-all:2.5.9'
implementation 'org.codehaus.groovy:groovy-all:2.5.14'
implementation 'com.amazonaws:aws-lambda-java-core:1.2.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.13.0'
testImplementation 'org.spockframework:spock-core:1.3-groovy-2.5'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package serverless.jvm.plugin

import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import groovy.transform.Canonical
import groovy.transform.CompileStatic
import groovy.transform.Memoized
Expand All @@ -22,6 +23,7 @@ class InvokeRequest {
if (!artifactFile.exists()) {
throw new Exception("Unable to find artifact: ${artifact}")
}
def classLoader = Thread.currentThread().getContextClassLoader();
final lambda = load(artifactFile.lastModified(), artifactFile, handler)

def result
Expand All @@ -31,9 +33,13 @@ class InvokeRequest {
} else {
result = LocalInvocation.invoke(lambda, data, function)
}
Thread.currentThread().setContextClassLoader(classLoader);

if (jsonOutput) {
try {
if (null != result && result.getClass().isAssignableFrom(ByteArrayOutputStream.class)) {
result = new JsonSlurper().parse(((ByteArrayOutputStream)result).toByteArray())
}
if (serverlessOffline) {
return JsonOutput.toJson(['__offline_payload__': result])
} else {
Expand All @@ -51,6 +57,7 @@ class InvokeRequest {
@Memoized
static LambdaFunction load(long lastModified, File artifact, String handler) {
final classLoader = LambdaClassLoader.getClassLoader(artifact)
Thread.currentThread().setContextClassLoader(classLoader)
final lambda = LambdaFunction.create(handler, classLoader)
return lambda
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class LambdaFunction {
Class returnType
Class parameterType
Boolean hasContext
Boolean hasOutput

static LambdaFunction create(String handler, ClassLoader classLoader) {
def (String className, String methodName) = handler.tokenize('::')
Expand All @@ -32,20 +33,27 @@ class LambdaFunction {
function.method = method.name
function.returnType = method.returnType
final parameterTypes = method.nativeParameterTypes
if (parameterTypes.size() < 1 || parameterTypes.size() > 2) {
throw new Exception("Handler method must have 1 or 2 parameters")
if (parameterTypes.size() < 1 || parameterTypes.size() > 3) {
throw new Exception("Handler method must have 1 to 3 parameters")
}

def first = parameterTypes?.size() > 0 ? parameterTypes[0] : null
def second = parameterTypes?.size() > 1 ? parameterTypes[1] : null
def third = parameterTypes?.size() > 2 ? parameterTypes[2] : null
function.parameterType = first
if (second != null && second != Context) {
throw new Exception("The second handler method parameter must be of type com.amazonaws.services.lambda.runtime.Context")
if ((second != null && third == null && second != Context) || (third != null && third != Context)) {
throw new Exception("The second or third handler method parameter must be of type com.amazonaws.services.lambda.runtime.Context")
}

if (second) {
if (second || third) {
function.hasContext = true
}
if (third) {
if (second != OutputStream || third != Context) {
throw new Exception("If third third handler method parameter is com.amazonaws.services.lambda.runtime.Context, second must be java.io.OutputStream")
}
function.hasOutput = true
}
} else {
if (!(RequestHandler.isAssignableFrom(clazz) || RequestStreamHandler.isAssignableFrom(clazz))) {
throw new Exception("Unable to detect handler method name")
Expand All @@ -56,6 +64,7 @@ class LambdaFunction {
function.returnType = method?.returnType
function.parameterType = method?.nativeParameterTypes?.first()
function.hasContext = true
function.hasOutput = false
}

function.instance = clazz.newInstance([].toArray())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,21 @@ class LocalInvocation {
static final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

static invoke(LambdaFunction lambda, String input, String functionName = null) {
def event, context, exception
try {
final parsed = new JsonSlurper().parseText(input)
if (parsed.event && parsed.context) {
event = parsed.event
context = parsed.context
}
} catch (e) {
exception = e
}

if (RequestStreamHandler.isAssignableFrom(lambda.clazz)) {
def inputStream = new ByteArrayInputStream(input.getBytes())
def inputStream = new ByteArrayInputStream(null != event ? JsonOutput.toJson(event).getBytes() : input.getBytes())
def outputStream = new ByteArrayOutputStream()
lambda.instance."${lambda.method}"(inputStream, outputStream, mockContext(lambda))
lambda.instance."${lambda.method}"(inputStream, outputStream, null != context ? newContext(context) : mockContext(lambda))
return outputStream
} else {
if (lambda.parameterType.isPrimitive() || isWrapperType(lambda.parameterType) || lambda.parameterType == String) {
Expand All @@ -24,34 +34,42 @@ class LocalInvocation {

if (input == null) {
if (lambda.hasContext) {
return lambda.instance."${lambda.method}"(input, mockContext(lambda.method))
if (lambda.hasOutput) {
def outputStream = new ByteArrayOutputStream()
return lambda.instance."${lambda.method}"(input, outputStream, mockContext(lambda.method))
} else {
return lambda.instance."${lambda.method}"(input, mockContext(lambda.method))
}
} else {
return lambda.instance."${lambda.method}"(input)
}
}

def event, context
try {
final parsed = new JsonSlurper().parseText(input)
if (parsed.event && parsed.context) {
event = parsed.event
context = parsed.context
}
} catch (e) {
if (null != exception) {
throw new Exception("Unable to convert input \"${input}\" to ${lambda.parameterType.name}")
}
if (event && context) {
final eventJson = JsonOutput.toJson(event)
final eventObject = mapper.readValue(eventJson, lambda.parameterType)
if (lambda.hasContext) {
return lambda.instance."${lambda.method}"(eventObject, newContext(context))
if (lambda.hasOutput) {
def outputStream = new ByteArrayOutputStream()
return lambda.instance."${lambda.method}"(eventObject, outputStream, newContext(context))
} else {
return lambda.instance."${lambda.method}"(eventObject, newContext(context))
}
} else {
return lambda.instance."${lambda.method}"(eventObject)
}
} else {
final objectInput = mapper.readValue(input, lambda.parameterType)
if (lambda.hasContext) {
return lambda.instance."${lambda.method}"(objectInput, mockContext(lambda.method))
if (lambda.hasOutput) {
def outputStream = new ByteArrayOutputStream()
return lambda.instance."${lambda.method}"(objectInput, outputStream, mockContext(lambda.method))
} else {
return lambda.instance."${lambda.method}"(objectInput, mockContext(lambda.method))
}
} else {
return lambda.instance."${lambda.method}"(objectInput)
}
Expand Down