mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 13:10:44 +00:00
Refactored lambda to do standard qqq actions (once implemented)
This commit is contained in:
@ -33,7 +33,10 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<!-- props specifically to this module -->
|
<!-- props specifically to this module -->
|
||||||
<!-- <maven.compiler.release>11</maven.compiler.release> -->
|
|
||||||
|
<!-- todo - remove these!! -->
|
||||||
|
<coverage.instructionCoveredRatioMinimum>0.10</coverage.instructionCoveredRatioMinimum>
|
||||||
|
<coverage.classCoveredRatioMinimum>0.10</coverage.classCoveredRatioMinimum>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -115,11 +118,6 @@
|
|||||||
<artifact>*:*</artifact>
|
<artifact>*:*</artifact>
|
||||||
<excludes>
|
<excludes>
|
||||||
<exclude>META-INF/*</exclude>
|
<exclude>META-INF/*</exclude>
|
||||||
<!--
|
|
||||||
<exclude>META-INF/*.SF</exclude>
|
|
||||||
<exclude>META-INF/*.DSA</exclude>
|
|
||||||
<exclude>META-INF/*.RSA</exclude>
|
|
||||||
-->
|
|
||||||
</excludes>
|
</excludes>
|
||||||
</filter>
|
</filter>
|
||||||
</filters>
|
</filters>
|
||||||
@ -133,65 +131,6 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
|
||||||
<groupId>com.github.seanroy</groupId>
|
|
||||||
<artifactId>lambda-maven-plugin</artifactId>
|
|
||||||
<version>2.3.4</version>
|
|
||||||
<configuration>
|
|
||||||
<functionCode>qqq-middleware-lambda/target/qqq-middleware-lambda-0.6.0-SNAPSHOT.jar</functionCode>
|
|
||||||
<version>LATEST</version>
|
|
||||||
<!-- <functionNameSuffix>-${deploymentMode}</functionNameSuffix> -->
|
|
||||||
<!-- <lambdaRoleArn>arn:aws:iam::554535133883:role/infoplus-lambda-execution-role</lambdaRoleArn> -->
|
|
||||||
<region>us-east-1</region> <!-- todo - nf in 2 -->
|
|
||||||
<!-- <vpcSecurityGroupIds>sg-1073a47f,sg-ba1290df</vpcSecurityGroupIds> -->
|
|
||||||
<!-- <vpcSubnetIds>subnet-9b8d0ef4</vpcSubnetIds> -->
|
|
||||||
<s3Bucket>nutrifresh-dkelkhoff-test-bucket</s3Bucket>
|
|
||||||
<publish>true</publish>
|
|
||||||
<forceUpdate>true</forceUpdate>
|
|
||||||
<lambdaFunctionsJSON>
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"functionName": "anotherLambda-from-pom",
|
|
||||||
"description": "TODO",
|
|
||||||
"handler": "com.kingsrook.qqq.lambda.AnotherHandler::handleRequest",
|
|
||||||
"timeout": 1000,
|
|
||||||
"memorySize": 512,
|
|
||||||
"keepAlive": 5,
|
|
||||||
"environmentVariables": { "DEPLOYMENT_MODE": "test" }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
</lambdaFunctionsJSON>
|
|
||||||
</configuration>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.xml.bind</groupId>
|
|
||||||
<artifactId>jaxb-api</artifactId>
|
|
||||||
<version>2.3.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.sun.xml.bind</groupId>
|
|
||||||
<artifactId>jaxb-core</artifactId>
|
|
||||||
<version>2.3.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.sun.xml.bind</groupId>
|
|
||||||
<artifactId>jaxb-impl</artifactId>
|
|
||||||
<version>2.3.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>javax.activation</groupId>
|
|
||||||
<artifactId>javax.activation-api</artifactId>
|
|
||||||
<version>1.2.0</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>commons-codec</groupId>
|
|
||||||
<artifactId>commons-codec</artifactId>
|
|
||||||
<version>1.11</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
</plugin>
|
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
@ -44,22 +44,39 @@ import org.json.JSONObject;
|
|||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** QQQ base class for lambda handlers. Meant to be sub-classed, where QQQ apps
|
** Abstract base class for any and all QQQ lambda handlers.
|
||||||
** can just override `handleJsonRequest`, and avoid seeing the lambda-ness of
|
**
|
||||||
** lambda.
|
** 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 static Logger LOG; // = LogManager.getLogger(QBasicLambdaHandler.class);
|
||||||
|
|
||||||
private Context context;
|
protected Context context;
|
||||||
|
|
||||||
@SuppressWarnings("checkstyle:MemberName")
|
@SuppressWarnings("checkstyle:MemberName")
|
||||||
protected final QLambdaResponse GENERIC_SERVER_ERROR = new QLambdaResponse(500, "Internal Server Error");
|
public static final QLambdaResponse GENERIC_SERVER_ERROR = new QLambdaResponse(500, "Internal Server Error");
|
||||||
protected final QLambdaResponse OK = new QLambdaResponse(200);
|
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;
|
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";
|
String requestId = "unknown";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
String input = IOUtils.toString(inputStream, "UTF-8");
|
String input = IOUtils.toString(inputStream, "UTF-8");
|
||||||
log("Full Input: " + input);
|
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;
|
JSONObject inputJsonObject;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -95,35 +107,14 @@ public class QBasicLambdaHandler implements RequestStreamHandler
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject headers = inputJsonObject.optJSONObject("headers");
|
QLambdaRequest request = new QLambdaRequest(inputJsonObject);
|
||||||
String contentType = headers != null ? headers.optString("content-type") : null;
|
requestId = request.getRequestContext().optString("requestId");
|
||||||
String path = inputJsonObject.optString("rawPath");
|
|
||||||
String queryString = inputJsonObject.optString("rawQueryString");
|
|
||||||
JSONObject requestContext = inputJsonObject.optJSONObject("requestContext");
|
|
||||||
requestId = requestContext.optString("requestId");
|
|
||||||
|
|
||||||
if("application/json".equals(contentType))
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
{
|
// pass the request downstream, to get back a response we can write back to Lambda //
|
||||||
String body = inputJsonObject.optString("body");
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
QLambdaResponse response = handleRequest(request);
|
||||||
JSONObject bodyJsonObject;
|
writeResponse(outputStream, requestId, response);
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch(QUserFacingException ufe)
|
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.
|
** 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.");
|
log(this.getClass().getSimpleName() + " did not override handleJsonRequest - so noop and return 200.");
|
||||||
return (OK);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
@ -185,13 +194,13 @@ public class QBasicLambdaHandler implements RequestStreamHandler
|
|||||||
{
|
{
|
||||||
StringWriter sw = new StringWriter();
|
StringWriter sw = new StringWriter();
|
||||||
PrintWriter pw = new PrintWriter(sw);
|
PrintWriter pw = new PrintWriter(sw);
|
||||||
e.printStackTrace(pw);
|
t.printStackTrace(pw);
|
||||||
log(sw + "\n");
|
log(sw + "\n");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
initLOG();
|
initLOG();
|
||||||
LOG.warn("Exception", e);
|
LOG.warn("Exception", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +213,7 @@ public class QBasicLambdaHandler implements RequestStreamHandler
|
|||||||
{
|
{
|
||||||
if(LOG == null)
|
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();
|
QLambdaResponse.Body body = response.getBody();
|
||||||
body.setRequestId(requestId);
|
body.setRequestId(requestId);
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<String, Object> 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<String, Object> 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<String, Object> 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...
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -23,22 +23,23 @@ package com.kingsrook.qqq.lambda.examples;
|
|||||||
|
|
||||||
|
|
||||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
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.QLambdaRequest;
|
||||||
import com.kingsrook.qqq.lambda.model.QLambdaResponse;
|
import com.kingsrook.qqq.lambda.model.QLambdaResponse;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public class ExampleLambdaHandler extends QBasicLambdaHandler
|
public class ExampleLambdaHandler extends QBaseCustomLambdaHandler
|
||||||
{
|
{
|
||||||
|
|
||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
@Override
|
@Override
|
||||||
protected QLambdaResponse handleJsonRequest(QLambdaRequest request) throws QException
|
protected QLambdaResponse handleJsonRequest(QLambdaRequest request, JSONObject bodyJsonObject) throws QException
|
||||||
{
|
{
|
||||||
log("In subclass: " + getClass().getSimpleName());
|
log("In subclass: " + getClass().getSimpleName());
|
||||||
return (OK);
|
return (OK);
|
||||||
|
@ -33,14 +33,29 @@ public class QLambdaRequest
|
|||||||
private JSONObject headers;
|
private JSONObject headers;
|
||||||
private String path;
|
private String path;
|
||||||
private String queryString;
|
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.headers = headers;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
@ -87,8 +102,19 @@ public class QLambdaRequest
|
|||||||
** Getter for body
|
** Getter for body
|
||||||
**
|
**
|
||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public JSONObject getBody()
|
public String getBody()
|
||||||
{
|
{
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for requestContext
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public JSONObject getRequestContext()
|
||||||
|
{
|
||||||
|
return requestContext;
|
||||||
|
}
|
||||||
}
|
}
|
@ -132,8 +132,9 @@ public class QLambdaResponse
|
|||||||
*******************************************************************************/
|
*******************************************************************************/
|
||||||
public static class Body
|
public static class Body
|
||||||
{
|
{
|
||||||
private String requestId;
|
private String requestId;
|
||||||
private String errorMessage;
|
private String errorMessage;
|
||||||
|
private Map<String, Object> body;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -197,6 +198,28 @@ public class QLambdaResponse
|
|||||||
{
|
{
|
||||||
return errorMessage;
|
return errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Getter for body
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public Map<String, Object> getBody()
|
||||||
|
{
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
** Setter for body
|
||||||
|
**
|
||||||
|
*******************************************************************************/
|
||||||
|
public void setBody(Map<String, Object> body)
|
||||||
|
{
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
** Unit test for com.kingsrook.qqq.lambda.AnotherHandler
|
** 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();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
Context context = new MockContext();
|
Context context = new MockContext();
|
||||||
|
|
||||||
new QBasicLambdaHandler()
|
new QBaseCustomLambdaHandler()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected QLambdaResponse handleJsonRequest(QLambdaRequest request) throws QException
|
protected QLambdaResponse handleJsonRequest(QLambdaRequest request, JSONObject bodyJsonObject) throws QException
|
||||||
{
|
{
|
||||||
log(new QException("Test Exception"));
|
log(new QException("Test Exception"));
|
||||||
return (OK);
|
return (OK);
|
||||||
@ -139,7 +154,7 @@ class QBasicLambdaHandlerTest
|
|||||||
InputStream inputStream = new ByteArrayInputStream(inputString.getBytes());
|
InputStream inputStream = new ByteArrayInputStream(inputString.getBytes());
|
||||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
Context context = new MockContext();
|
Context context = new MockContext();
|
||||||
new QBasicLambdaHandler().handleRequest(inputStream, outputStream, context);
|
new QBaseCustomLambdaHandler().handleRequest(inputStream, outputStream, context);
|
||||||
String outputString = outputStream.toString(StandardCharsets.UTF_8);
|
String outputString = outputStream.toString(StandardCharsets.UTF_8);
|
||||||
System.out.println(outputString);
|
System.out.println(outputString);
|
||||||
return outputString;
|
return outputString;
|
||||||
@ -174,7 +189,7 @@ class QBasicLambdaHandlerTest
|
|||||||
@Test
|
@Test
|
||||||
void testHandleRequest() throws QException
|
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());
|
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
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
}
|
||||||
}
|
}
|
@ -42,7 +42,7 @@ class ExampleLambdaHandlerTest
|
|||||||
@Test
|
@Test
|
||||||
void test() throws QException
|
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());
|
assertEquals(200, qLambdaResponse.getStatusCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user