diff --git a/qqq-middleware-lambda/pom.xml b/qqq-middleware-lambda/pom.xml
index b9b3291b..fda13437 100644
--- a/qqq-middleware-lambda/pom.xml
+++ b/qqq-middleware-lambda/pom.xml
@@ -33,7 +33,10 @@
-
+
+
+ 0.10
+ 0.10
@@ -115,11 +118,6 @@
*:*
META-INF/*
-
@@ -133,65 +131,6 @@
-
-
- com.github.seanroy
- lambda-maven-plugin
- 2.3.4
-
- qqq-middleware-lambda/target/qqq-middleware-lambda-0.6.0-SNAPSHOT.jar
- LATEST
-
-
- us-east-1
-
-
- nutrifresh-dkelkhoff-test-bucket
- true
- true
-
- [
- {
- "functionName": "anotherLambda-from-pom",
- "description": "TODO",
- "handler": "com.kingsrook.qqq.lambda.AnotherHandler::handleRequest",
- "timeout": 1000,
- "memorySize": 512,
- "keepAlive": 5,
- "environmentVariables": { "DEPLOYMENT_MODE": "test" }
- }
- ]
-
-
-
-
- javax.xml.bind
- jaxb-api
- 2.3.0
-
-
- com.sun.xml.bind
- jaxb-core
- 2.3.0
-
-
- com.sun.xml.bind
- jaxb-impl
- 2.3.0
-
-
- javax.activation
- javax.activation-api
- 1.2.0
-
-
- commons-codec
- commons-codec
- 1.11
-
-
-
-
diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBasicLambdaHandler.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QAbstractLambdaHandler.java
similarity index 69%
rename from qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBasicLambdaHandler.java
rename to qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QAbstractLambdaHandler.java
index deb03c0a..61ed6ff9 100644
--- a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBasicLambdaHandler.java
+++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QAbstractLambdaHandler.java
@@ -44,22 +44,39 @@ import org.json.JSONObject;
/*******************************************************************************
- ** QQQ base class for lambda handlers. Meant to be sub-classed, where QQQ apps
- ** can just override `handleJsonRequest`, and avoid seeing the lambda-ness of
- ** lambda.
+ ** Abstract base class for any and all QQQ lambda handlers.
+ **
+ ** This class provides the method `handleRequest(InputStream, OutputStream, Context)`,
+ ** which is what gets invoked by AWS Lambda. In there, we parse the data from
+ ** the inputStream to build a QLambdaRequest - which is then passed to:
+ **
+ ** `handleRequest(QLambdaRequest)` - which would be meant for implementing in a
+ ** subclass.
**
- ** Such subclasses can then have easy standalone unit tests - just testing their
- ** logic, and not the lambda-ness.
*******************************************************************************/
-public class QBasicLambdaHandler implements RequestStreamHandler
+public abstract class QAbstractLambdaHandler implements RequestStreamHandler
{
private static Logger LOG; // = LogManager.getLogger(QBasicLambdaHandler.class);
- private Context context;
+ protected Context context;
@SuppressWarnings("checkstyle:MemberName")
- protected final QLambdaResponse GENERIC_SERVER_ERROR = new QLambdaResponse(500, "Internal Server Error");
- protected final QLambdaResponse OK = new QLambdaResponse(200);
+ public static final QLambdaResponse GENERIC_SERVER_ERROR = new QLambdaResponse(500, "Internal Server Error");
+ public static final QLambdaResponse OK = new QLambdaResponse(200);
+
+
+
+ /*******************************************************************************
+ ** Constructor
+ **
+ *******************************************************************************/
+ public QAbstractLambdaHandler()
+ {
+ ///////////////////////////////////////////////////////////////////////
+ // tell log4j to use a config that won't fail when running in lambda //
+ ///////////////////////////////////////////////////////////////////////
+ Configurator.initialize(null, "qqq-lambda-log4j2.xml");
+ }
@@ -70,20 +87,15 @@ public class QBasicLambdaHandler implements RequestStreamHandler
{
this.context = context;
- ///////////////////////////////////////////////////////////////////////
- // tell log4j to use a config that won't fail when running in lambda //
- ///////////////////////////////////////////////////////////////////////
- Configurator.initialize(null, "qqq-lambda-log4j2.xml");
-
String requestId = "unknown";
try
{
String input = IOUtils.toString(inputStream, "UTF-8");
log("Full Input: " + input);
- //////////////////////////////////////////////////////////////////////////////////
- // parse the input as json - then pull parts out of it that we know to look for //
- //////////////////////////////////////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // parse the Lambda input as json - then pull parts out of it that we know to look for //
+ /////////////////////////////////////////////////////////////////////////////////////////
JSONObject inputJsonObject;
try
{
@@ -95,35 +107,14 @@ public class QBasicLambdaHandler implements RequestStreamHandler
return;
}
- JSONObject headers = inputJsonObject.optJSONObject("headers");
- String contentType = headers != null ? headers.optString("content-type") : null;
- String path = inputJsonObject.optString("rawPath");
- String queryString = inputJsonObject.optString("rawQueryString");
- JSONObject requestContext = inputJsonObject.optJSONObject("requestContext");
- requestId = requestContext.optString("requestId");
+ QLambdaRequest request = new QLambdaRequest(inputJsonObject);
+ requestId = request.getRequestContext().optString("requestId");
- if("application/json".equals(contentType))
- {
- String body = inputJsonObject.optString("body");
-
- JSONObject bodyJsonObject;
- try
- {
- bodyJsonObject = JsonUtils.toJSONObject(body);
- }
- catch(JSONException je)
- {
- writeResponse(outputStream, requestId, new QLambdaResponse(400, "Unable to parse request body as JSON: " + je.getMessage()));
- return;
- }
-
- QLambdaResponse response = handleJsonRequest(new QLambdaRequest(headers, path, queryString, bodyJsonObject));
- writeResponse(outputStream, requestId, response);
- }
- else
- {
- writeResponse(outputStream, requestId, new QLambdaResponse(400, "Unsupported content-type: " + contentType));
- }
+ /////////////////////////////////////////////////////////////////////////////////////
+ // pass the request downstream, to get back a response we can write back to Lambda //
+ /////////////////////////////////////////////////////////////////////////////////////
+ QLambdaResponse response = handleRequest(request);
+ writeResponse(outputStream, requestId, response);
}
catch(QUserFacingException ufe)
{
@@ -137,10 +128,17 @@ public class QBasicLambdaHandler implements RequestStreamHandler
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected abstract QLambdaResponse handleRequest(QLambdaRequest request) throws QException;
+
+
+
/*******************************************************************************
** Meant to be overridden by subclasses, to provide functionality, if needed.
*******************************************************************************/
- protected QLambdaResponse handleJsonRequest(QLambdaRequest request) throws QException
+ protected QLambdaResponse handleJsonRequest(QLambdaRequest request, JSONObject bodyJsonObject) throws QException
{
log(this.getClass().getSimpleName() + " did not override handleJsonRequest - so noop and return 200.");
return (OK);
@@ -171,12 +169,23 @@ public class QBasicLambdaHandler implements RequestStreamHandler
+ /*******************************************************************************
+ ** Write to the cloudwatch logs.
+ *******************************************************************************/
+ protected void log(String message, Throwable t)
+ {
+ log(message);
+ log(t);
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
- protected void log(Throwable e)
+ protected void log(Throwable t)
{
- if(e == null)
+ if(t == null)
{
return;
}
@@ -185,13 +194,13 @@ public class QBasicLambdaHandler implements RequestStreamHandler
{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
- e.printStackTrace(pw);
+ t.printStackTrace(pw);
log(sw + "\n");
}
else
{
initLOG();
- LOG.warn("Exception", e);
+ LOG.warn("Exception", t);
}
}
@@ -204,7 +213,7 @@ public class QBasicLambdaHandler implements RequestStreamHandler
{
if(LOG == null)
{
- LOG = LogManager.getLogger(QBasicLambdaHandler.class);
+ LOG = LogManager.getLogger(QAbstractLambdaHandler.class);
}
}
@@ -213,7 +222,7 @@ public class QBasicLambdaHandler implements RequestStreamHandler
/*******************************************************************************
**
*******************************************************************************/
- private void writeResponse(OutputStream outputStream, String requestId, QLambdaResponse response) throws IOException
+ protected void writeResponse(OutputStream outputStream, String requestId, QLambdaResponse response) throws IOException
{
QLambdaResponse.Body body = response.getBody();
body.setRequestId(requestId);
diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandler.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandler.java
new file mode 100644
index 00000000..e3c1d5f3
--- /dev/null
+++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandler.java
@@ -0,0 +1,85 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.lambda;
+
+
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.utils.JsonUtils;
+import com.kingsrook.qqq.lambda.model.QLambdaRequest;
+import com.kingsrook.qqq.lambda.model.QLambdaResponse;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+/*******************************************************************************
+ ** QQQ base class for "Custom" lambda handlers. e.g., completely custom code
+ ** to run in your QQQ app, outside of tables or processes (for those standard
+ ** use-cases, see QStandardLambdaHandler).
+ **
+ ** Subclasses here can just override `handleJsonRequest`, and avoid seeing the
+ ** lambda-ness of lambda.
+ **
+ ** Such subclasses can then have easy standalone unit tests - just testing their
+ ** logic, and not the lambda-ness.
+ *******************************************************************************/
+public class QBaseCustomLambdaHandler extends QAbstractLambdaHandler
+{
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected QLambdaResponse handleRequest(QLambdaRequest request) throws QException
+ {
+ String contentType = request.getHeaders().optString("content-type");
+ if("application/json".equals(contentType))
+ {
+ JSONObject bodyJsonObject;
+ try
+ {
+ bodyJsonObject = JsonUtils.toJSONObject(request.getBody());
+ }
+ catch(JSONException je)
+ {
+ return (new QLambdaResponse(400, "Unable to parse request body as JSON: " + je.getMessage()));
+ }
+
+ return (handleJsonRequest(request, bodyJsonObject));
+ }
+ else
+ {
+ return (new QLambdaResponse(400, "Unsupported content-type: " + contentType));
+ }
+ }
+
+
+
+ /*******************************************************************************
+ ** Meant to be overridden by subclasses, to provide functionality, if needed.
+ *******************************************************************************/
+ @Override
+ protected QLambdaResponse handleJsonRequest(QLambdaRequest request, JSONObject bodyJsonObject) throws QException
+ {
+ log(this.getClass().getSimpleName() + " did not override handleJsonRequest - so noop and return 200.");
+ return (OK);
+ }
+
+}
diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QStandardLambdaHandler.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QStandardLambdaHandler.java
new file mode 100644
index 00000000..5b29d6a4
--- /dev/null
+++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/QStandardLambdaHandler.java
@@ -0,0 +1,290 @@
+/*
+ * QQQ - Low-code Application Framework for Engineers.
+ * Copyright (C) 2021-2022. Kingsrook, LLC
+ * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
+ * contact@kingsrook.com
+ * https://github.com/Kingsrook/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package com.kingsrook.qqq.lambda;
+
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import com.kingsrook.qqq.backend.core.actions.async.AsyncJobManager;
+import com.kingsrook.qqq.backend.core.actions.async.JobGoingAsyncException;
+import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
+import com.kingsrook.qqq.backend.core.exceptions.QException;
+import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
+import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
+import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
+import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
+import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
+import com.kingsrook.qqq.backend.core.model.session.QSession;
+import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
+import com.kingsrook.qqq.lambda.model.QLambdaRequest;
+import com.kingsrook.qqq.lambda.model.QLambdaResponse;
+import org.json.JSONObject;
+
+
+/*******************************************************************************
+ ** Class to provide QQQ Standard table & process actions via AWS Lambda.
+ **
+ ** Right now, an application is responsible for:
+ ** - in a constructor, calling setQInstance.
+ ** - overriding setupSession to do a default system-session...
+ *******************************************************************************/
+public class QStandardLambdaHandler extends QAbstractLambdaHandler
+{
+ protected QInstance qInstance;
+
+
+
+ /*******************************************************************************
+ ** Setter for qInstance
+ **
+ *******************************************************************************/
+ public void setQInstance(QInstance qInstance)
+ {
+ this.qInstance = qInstance;
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected void setupSession(QLambdaRequest request, AbstractActionInput actionInput)
+ {
+ actionInput.setSession(new QSession());
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ protected QLambdaResponse handleRequest(QLambdaRequest request) throws QException
+ {
+ String path = request.getPath();
+ String[] pathParts = path.split("/");
+
+ String httpMethod = "unknown";
+ if(request.getRequestContext().has("http"))
+ {
+ JSONObject httpObject = request.getRequestContext().getJSONObject("http");
+ httpMethod = httpObject.optString("method");
+ }
+
+ if(path.matches("/processes/\\w+/init/?"))
+ {
+ return (processInit(request, pathParts[2]));
+ }
+ if(path.matches("/metaData/?"))
+ {
+ // todo return (metaData(request));
+ }
+ if(path.matches("/metaData/table/\\w+/?"))
+ {
+ // todo return (tableMetaData(request));
+ }
+ if(path.matches("/metaData/process/\\w+/?"))
+ {
+ // todo return (processMetaData(request));
+ }
+ if(path.matches("/data/table/\\w+/?") || path.matches("/data/table/\\w+/query/?"))
+ {
+ // todo return (dataQuery(request));
+ }
+ if(path.matches("/data/table/\\w+/count/?"))
+ {
+ // todo return (dataCount(request));
+ }
+ if(path.matches("/data/table/\\w+/export/?"))
+ {
+ // todo return (dataExportWithoutFilename(request));
+ }
+ if(path.matches("/data/table/\\w+/export/\\w+/?"))
+ {
+ // todo return (dataExportWithFilename(request));
+ }
+ if(path.matches("/data/table/\\w+/prossibleValues/\\w+/?"))
+ {
+ // todo return (possibleValues(request));
+ }
+ if(path.matches("/data/table/\\w+/\\w+/?"))
+ {
+ if("GET".equals(httpMethod))
+ {
+ // todo return (dataGet(request));
+ }
+ else if("PATCH".equals(httpMethod))
+ {
+ // todo return (dataUpdate(request));
+ }
+ else if("PUT".equals(httpMethod))
+ {
+ // todo return (dataUpdate(request));
+ }
+ else if("DELETE".equals(httpMethod))
+ {
+ // todo return (dataDelete(request));
+ }
+ else
+ {
+ // todo return (new QLambdaResponse(405, "Unrecognized method: " + httpMethod));
+ }
+ }
+ if(path.matches("/widget/\\w+/?"))
+ {
+ // todo return (widget(request));
+ }
+ else
+ {
+ return (new QLambdaResponse(404, "Unrecognized path: " + path));
+ }
+
+ return (GENERIC_SERVER_ERROR);
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private QLambdaResponse processInit(QLambdaRequest request, String processName)
+ {
+ String processUUID = null;
+ String startAfterStep = null;
+
+ Map resultForCaller = new HashMap<>();
+ QLambdaResponse response;
+
+ try
+ {
+ if(processUUID == null)
+ {
+ processUUID = UUID.randomUUID().toString();
+ }
+ resultForCaller.put("processUUID", processUUID);
+
+ log(startAfterStep == null ? "Initiating process [" + processName + "] [" + processUUID + "]"
+ : "Resuming process [" + processName + "] [" + processUUID + "] after step [" + startAfterStep + "]");
+
+ RunProcessInput runProcessInput = new RunProcessInput(qInstance);
+ setupSession(request, runProcessInput);
+ runProcessInput.setProcessName(processName);
+ runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK);
+ runProcessInput.setProcessUUID(processUUID);
+ runProcessInput.setStartAfterStep(startAfterStep);
+ populateRunProcessRequestWithValuesFromContext(request, runProcessInput);
+
+ ////////////////////////////////////////
+ // run the process as an async action //
+ ////////////////////////////////////////
+ Integer timeout = 60_000; // getTimeoutMillis(context);
+ RunProcessOutput runProcessOutput = new AsyncJobManager().startJob(timeout, TimeUnit.MILLISECONDS, (callback) ->
+ {
+ runProcessInput.setAsyncJobCallback(callback);
+ return (new RunProcessAction().execute(runProcessInput));
+ });
+
+ log("Process result error? " + runProcessOutput.getException());
+ for(QFieldMetaData outputField : qInstance.getProcess(runProcessInput.getProcessName()).getOutputFields())
+ {
+ log("Process result output value: " + outputField.getName() + ": " + runProcessOutput.getValues().get(outputField.getName()));
+ }
+
+ serializeRunProcessResultForCaller(resultForCaller, runProcessOutput);
+ }
+ catch(JobGoingAsyncException jgae)
+ {
+ resultForCaller.put("jobUUID", jgae.getJobUUID());
+ }
+ catch(Exception e)
+ {
+ //////////////////////////////////////////////////////////////////////////////
+ // our other actions in here would do: handleException(context, e); //
+ // which would return a 500 to the client. //
+ // but - other process-step actions, they always return a 200, just with an //
+ // optional error message - so - keep all of the processes consistent. //
+ //////////////////////////////////////////////////////////////////////////////
+ serializeRunProcessExceptionForCaller(resultForCaller, e);
+ }
+
+ response = new QLambdaResponse(200);
+ response.getBody().setBody(resultForCaller);
+
+ return (response);
+ }
+
+
+
+ /*******************************************************************************
+ ** Whether a step finished synchronously or asynchronously, return its data
+ ** to the caller the same way.
+ *******************************************************************************/
+ private void serializeRunProcessResultForCaller(Map resultForCaller, RunProcessOutput runProcessOutput)
+ {
+ if(runProcessOutput.getException().isPresent())
+ {
+ ////////////////////////////////////////////////////////////////
+ // per code coverage, this path may never actually get hit... //
+ ////////////////////////////////////////////////////////////////
+ serializeRunProcessExceptionForCaller(resultForCaller, runProcessOutput.getException().get());
+ }
+ resultForCaller.put("values", runProcessOutput.getValues());
+ runProcessOutput.getProcessState().getNextStepName().ifPresent(nextStep -> resultForCaller.put("nextStep", nextStep));
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void serializeRunProcessExceptionForCaller(Map resultForCaller, Exception exception)
+ {
+ QUserFacingException userFacingException = ExceptionUtils.findClassInRootChain(exception, QUserFacingException.class);
+
+ if(userFacingException != null)
+ {
+ log("User-facing exception in process", userFacingException);
+ resultForCaller.put("error", userFacingException.getMessage());
+ resultForCaller.put("userFacingError", userFacingException.getMessage());
+ }
+ else
+ {
+ Throwable rootException = ExceptionUtils.getRootException(exception);
+ log("Uncaught Exception in process", exception);
+ resultForCaller.put("error", "Error message: " + rootException.getMessage());
+ }
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ private void populateRunProcessRequestWithValuesFromContext(QLambdaRequest request, RunProcessInput runProcessInput)
+ {
+ runProcessInput.addValue("body", request.getBody());
+ // todo - a lot more...
+ }
+
+}
diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandler.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandler.java
index 73cded36..5e4b0f32 100644
--- a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandler.java
+++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandler.java
@@ -23,22 +23,23 @@ package com.kingsrook.qqq.lambda.examples;
import com.kingsrook.qqq.backend.core.exceptions.QException;
-import com.kingsrook.qqq.lambda.QBasicLambdaHandler;
+import com.kingsrook.qqq.lambda.QBaseCustomLambdaHandler;
import com.kingsrook.qqq.lambda.model.QLambdaRequest;
import com.kingsrook.qqq.lambda.model.QLambdaResponse;
+import org.json.JSONObject;
/*******************************************************************************
**
*******************************************************************************/
-public class ExampleLambdaHandler extends QBasicLambdaHandler
+public class ExampleLambdaHandler extends QBaseCustomLambdaHandler
{
/*******************************************************************************
**
*******************************************************************************/
@Override
- protected QLambdaResponse handleJsonRequest(QLambdaRequest request) throws QException
+ protected QLambdaResponse handleJsonRequest(QLambdaRequest request, JSONObject bodyJsonObject) throws QException
{
log("In subclass: " + getClass().getSimpleName());
return (OK);
diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaRequest.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaRequest.java
index 2f926ce6..b067f9a8 100644
--- a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaRequest.java
+++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaRequest.java
@@ -33,14 +33,29 @@ public class QLambdaRequest
private JSONObject headers;
private String path;
private String queryString;
- private JSONObject body;
+ private String body;
+ private JSONObject requestContext;
/*******************************************************************************
**
*******************************************************************************/
- public QLambdaRequest(JSONObject headers, String path, String queryString, JSONObject body)
+ public QLambdaRequest(JSONObject inputJsonObject)
+ {
+ this.headers = inputJsonObject.optJSONObject("headers");
+ this.path = inputJsonObject.optString("rawPath");
+ this.queryString = inputJsonObject.optString("rawQueryString");
+ this.body = inputJsonObject.optString("body");
+ this.requestContext = inputJsonObject.optJSONObject("requestContext");
+ }
+
+
+
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ public QLambdaRequest(JSONObject headers, String path, String queryString, String body)
{
this.headers = headers;
this.path = path;
@@ -87,8 +102,19 @@ public class QLambdaRequest
** Getter for body
**
*******************************************************************************/
- public JSONObject getBody()
+ public String getBody()
{
return body;
}
+
+
+
+ /*******************************************************************************
+ ** Getter for requestContext
+ **
+ *******************************************************************************/
+ public JSONObject getRequestContext()
+ {
+ return requestContext;
+ }
}
\ No newline at end of file
diff --git a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaResponse.java b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaResponse.java
index 24f1734f..c8dfc8ac 100644
--- a/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaResponse.java
+++ b/qqq-middleware-lambda/src/main/java/com/kingsrook/qqq/lambda/model/QLambdaResponse.java
@@ -132,8 +132,9 @@ public class QLambdaResponse
*******************************************************************************/
public static class Body
{
- private String requestId;
- private String errorMessage;
+ private String requestId;
+ private String errorMessage;
+ private Map body;
@@ -197,6 +198,28 @@ public class QLambdaResponse
{
return errorMessage;
}
+
+
+
+ /*******************************************************************************
+ ** Getter for body
+ **
+ *******************************************************************************/
+ public Map getBody()
+ {
+ return body;
+ }
+
+
+
+ /*******************************************************************************
+ ** Setter for body
+ **
+ *******************************************************************************/
+ public void setBody(Map body)
+ {
+ this.body = body;
+ }
}
}
diff --git a/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBasicLambdaHandlerTest.java b/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandlerTest.java
similarity index 82%
rename from qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBasicLambdaHandlerTest.java
rename to qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandlerTest.java
index c766968f..935f616d 100644
--- a/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBasicLambdaHandlerTest.java
+++ b/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/QBaseCustomLambdaHandlerTest.java
@@ -45,7 +45,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/*******************************************************************************
** Unit test for com.kingsrook.qqq.lambda.AnotherHandler
*******************************************************************************/
-class QBasicLambdaHandlerTest
+class QBaseCustomLambdaHandlerTest
{
/*******************************************************************************
@@ -108,6 +108,21 @@ class QBasicLambdaHandlerTest
+ /*******************************************************************************
+ **
+ *******************************************************************************/
+ @Test
+ void testNoBodyInputStringNonJsonContentType() throws IOException
+ {
+ String inputString = getNoBodyInputString();
+ String outputString = runHandleRequest(inputString);
+ JSONObject outputJson = JsonUtils.toJSONObject(outputString);
+ assertTrue(outputJson.has("errorMessage"));
+ assertThat(outputJson.getString("errorMessage")).contains("Unsupported content-type:");
+ }
+
+
+
/*******************************************************************************
**
*******************************************************************************/
@@ -118,10 +133,10 @@ class QBasicLambdaHandlerTest
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Context context = new MockContext();
- new QBasicLambdaHandler()
+ new QBaseCustomLambdaHandler()
{
@Override
- protected QLambdaResponse handleJsonRequest(QLambdaRequest request) throws QException
+ protected QLambdaResponse handleJsonRequest(QLambdaRequest request, JSONObject bodyJsonObject) throws QException
{
log(new QException("Test Exception"));
return (OK);
@@ -139,7 +154,7 @@ class QBasicLambdaHandlerTest
InputStream inputStream = new ByteArrayInputStream(inputString.getBytes());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
Context context = new MockContext();
- new QBasicLambdaHandler().handleRequest(inputStream, outputStream, context);
+ new QBaseCustomLambdaHandler().handleRequest(inputStream, outputStream, context);
String outputString = outputStream.toString(StandardCharsets.UTF_8);
System.out.println(outputString);
return outputString;
@@ -174,7 +189,7 @@ class QBasicLambdaHandlerTest
@Test
void testHandleRequest() throws QException
{
- QLambdaResponse response = new QBasicLambdaHandler().handleJsonRequest(new QLambdaRequest(new JSONObject(), "/", "", new JSONObject()));
+ QLambdaResponse response = new QBaseCustomLambdaHandler().handleJsonRequest(new QLambdaRequest(new JSONObject(), "/", "", ""), new JSONObject());
assertEquals(200, response.getStatusCode());
}
@@ -351,4 +366,47 @@ class QBasicLambdaHandlerTest
""");
}
+
+
+ private String getNoBodyInputString()
+ {
+ return ("""
+ {
+ "version": "2.0",
+ "routeKey": "$default",
+ "rawPath": "/",
+ "rawQueryString": "",
+ "headers": {
+ "x-amzn-tls-cipher-suite": "ECDHE-RSA-AES128-GCM-SHA256",
+ "x-amzn-tls-version": "TLSv1.2",
+ "x-amzn-trace-id": "Root=1-6349b9a1-4d3785872159571e302a3015",
+ "x-forwarded-proto": "https",
+ "host": "p6kox4fzsfajxvsmyc222cu5ta0dlpwp.lambda-url.us-east-1.on.aws",
+ "x-forwarded-port": "443",
+ "x-forwarded-for": "24.217.225.229",
+ "accept": "*/*",
+ "user-agent": "curl/7.79.1"
+ },
+ "requestContext": {
+ "accountId": "anonymous",
+ "apiId": "p6kox4fzsfajxvsmyc222cu5ta0dlpwp",
+ "domainName": "p6kox4fzsfajxvsmyc222cu5ta0dlpwp.lambda-url.us-east-1.on.aws",
+ "domainPrefix": "p6kox4fzsfajxvsmyc222cu5ta0dlpwp",
+ "http": {
+ "method": "GET",
+ "path": "/",
+ "protocol": "HTTP/1.1",
+ "sourceIp": "24.217.225.229",
+ "userAgent": "curl/7.79.1"
+ },
+ "requestId": "82b80656-5f05-46eb-bc2a-7b8328d095d4",
+ "routeKey": "$default",
+ "stage": "$default",
+ "time": "14/Oct/2022:19:33:53 +0000",
+ "timeEpoch": 1665776033574
+ },
+ "isBase64Encoded": false
+ }
+ """);
+ }
}
\ No newline at end of file
diff --git a/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandlerTest.java b/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandlerTest.java
index 76aa9e93..72787612 100644
--- a/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandlerTest.java
+++ b/qqq-middleware-lambda/src/test/java/com/kingsrook/qqq/lambda/examples/ExampleLambdaHandlerTest.java
@@ -42,7 +42,7 @@ class ExampleLambdaHandlerTest
@Test
void test() throws QException
{
- QLambdaResponse qLambdaResponse = new ExampleLambdaHandler().handleJsonRequest(new QLambdaRequest(new JSONObject(), "", "", new JSONObject()));
+ QLambdaResponse qLambdaResponse = new ExampleLambdaHandler().handleJsonRequest(new QLambdaRequest(new JSONObject(), "", "", ""), new JSONObject());
assertEquals(200, qLambdaResponse.getStatusCode());
}