mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-18 05:01:07 +00:00
Add convertObjectToJava to code executors - for converting language objects to java objects
This commit is contained in:
@ -101,6 +101,17 @@ public class ExecuteCodeAction
|
||||
context.putAll(input.getInput());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// set the qCodeExecutor into any context objects which are QCodeExecutorAware //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
for(Serializable value : context.values())
|
||||
{
|
||||
if(value instanceof QCodeExecutorAware qCodeExecutorAware)
|
||||
{
|
||||
qCodeExecutorAware.setQCodeExecutor(qCodeExecutor);
|
||||
}
|
||||
}
|
||||
|
||||
Serializable codeOutput = qCodeExecutor.execute(codeReference, context, executionLogger);
|
||||
output.setOutput(codeOutput);
|
||||
executionLogger.acceptExecutionEnd(codeOutput);
|
||||
|
@ -41,4 +41,14 @@ public interface QCodeExecutor
|
||||
*******************************************************************************/
|
||||
Serializable execute(QCodeReference codeReference, Map<String, Serializable> inputContext, QCodeExecutionLoggerInterface executionLogger) throws QCodeException;
|
||||
|
||||
/*******************************************************************************
|
||||
** Process an object from the script's language/runtime into a (more) native java object.
|
||||
** e.g., a Nashorn ScriptObjectMirror will end up as a "primitive", or a List or Map of such
|
||||
**
|
||||
*******************************************************************************/
|
||||
default Object convertObjectToJava(Object object)
|
||||
{
|
||||
return (object);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. 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.backend.core.actions.scripts;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface for classes that can accept a QCodeExecutor object via a setter.
|
||||
*******************************************************************************/
|
||||
public interface QCodeExecutorAware
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void setQCodeExecutor(QCodeExecutor qCodeExecutor);
|
||||
|
||||
}
|
@ -27,6 +27,10 @@ import javax.script.ScriptEngine;
|
||||
import javax.script.ScriptEngineManager;
|
||||
import javax.script.ScriptException;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.QCodeExecutor;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
||||
@ -36,8 +40,10 @@ import com.kingsrook.qqq.backend.core.utils.ExceptionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory;
|
||||
import org.openjdk.nashorn.api.scripting.ScriptObjectMirror;
|
||||
import org.openjdk.nashorn.internal.runtime.ECMAException;
|
||||
import org.openjdk.nashorn.internal.runtime.ParserException;
|
||||
import org.openjdk.nashorn.internal.runtime.Undefined;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -59,6 +65,59 @@ public class QJavaScriptExecutor implements QCodeExecutor
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Object convertObjectToJava(Object object)
|
||||
{
|
||||
if(object == null || object instanceof String || object instanceof Boolean || object instanceof Integer || object instanceof Long || object instanceof BigDecimal)
|
||||
{
|
||||
return (object);
|
||||
}
|
||||
else if(object instanceof Float f)
|
||||
{
|
||||
return (new BigDecimal(f));
|
||||
}
|
||||
else if(object instanceof Double d)
|
||||
{
|
||||
return (new BigDecimal(d));
|
||||
}
|
||||
else if(object instanceof Undefined)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// well, we always said we wanted javascript to treat null & undefined the same way... here's our chance //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (null);
|
||||
}
|
||||
|
||||
if(object instanceof ScriptObjectMirror scriptObjectMirror)
|
||||
{
|
||||
if(scriptObjectMirror.isArray())
|
||||
{
|
||||
List<Object> result = new ArrayList<>();
|
||||
for(String key : scriptObjectMirror.keySet())
|
||||
{
|
||||
result.add(Integer.parseInt(key), convertObjectToJava(scriptObjectMirror.get(key)));
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
for(String key : scriptObjectMirror.keySet())
|
||||
{
|
||||
result.put(key, convertObjectToJava(scriptObjectMirror.get(key)));
|
||||
}
|
||||
return (result);
|
||||
}
|
||||
}
|
||||
|
||||
return QCodeExecutor.super.convertObjectToJava(object);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -23,7 +23,12 @@ package com.kingsrook.qqq.languages.javascript;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.ExecuteCodeAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.QCodeExecutor;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.QCodeExecutorAware;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QCodeException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
|
||||
@ -31,6 +36,7 @@ import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
|
||||
@ -241,10 +247,50 @@ class ExecuteCodeActionTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Test
|
||||
void testConvertObjectToJava() throws QException
|
||||
{
|
||||
TestQCodeExecutorAware converter = new TestQCodeExecutorAware();
|
||||
testOne(1, """
|
||||
converter.convertObject("one", 1);
|
||||
converter.convertObject("two", "two");
|
||||
converter.convertObject("true", true);
|
||||
converter.convertObject("null", null);
|
||||
converter.convertObject("undefined", undefined);
|
||||
converter.convertObject("flatMap", {"a": 1, "b": "c"});
|
||||
converter.convertObject("flatList", ["a", 1, "b", "c"]);
|
||||
converter.convertObject("mixedMap", {"a": [1, {"2": "3"}], "b": {"c": ["d"]}});
|
||||
""", MapBuilder.of("converter", converter));
|
||||
|
||||
assertEquals(1, converter.getConvertedObject("one"));
|
||||
assertEquals("two", converter.getConvertedObject("two"));
|
||||
assertEquals(true, converter.getConvertedObject("true"));
|
||||
assertNull(converter.getConvertedObject("null"));
|
||||
assertNull(converter.getConvertedObject("undefined"));
|
||||
assertEquals(Map.of("a", 1, "b", "c"), converter.getConvertedObject("flatMap"));
|
||||
assertEquals(List.of("a", 1, "b", "c"), converter.getConvertedObject("flatList"));
|
||||
assertEquals(Map.of("a", List.of(1, Map.of("2", "3")), "b", Map.of("c", List.of("d"))), converter.getConvertedObject("mixedMap"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private OneTestOutput testOne(Integer inputValueC, String code) throws QException
|
||||
{
|
||||
return (testOne(inputValueC, code, null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private OneTestOutput testOne(Integer inputValueC, String code, Map<String, Serializable> additionalContext) throws QException
|
||||
{
|
||||
System.out.println();
|
||||
QInstance instance = TestUtils.defineInstance();
|
||||
@ -259,6 +305,14 @@ class ExecuteCodeActionTest extends BaseTest
|
||||
input.withContext("input", testInput);
|
||||
input.withContext("output", testOutput);
|
||||
|
||||
if(additionalContext != null)
|
||||
{
|
||||
for(Map.Entry<String, Serializable> entry : additionalContext.entrySet())
|
||||
{
|
||||
input.withContext(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
ExecuteCodeOutput output = new ExecuteCodeOutput();
|
||||
|
||||
ExecuteCodeAction executeCodeAction = new ExecuteCodeAction();
|
||||
@ -269,6 +323,49 @@ class ExecuteCodeActionTest extends BaseTest
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class TestQCodeExecutorAware implements QCodeExecutorAware, Serializable
|
||||
{
|
||||
private QCodeExecutor qCodeExecutor;
|
||||
|
||||
private Map<String, Object> convertedObjectMap = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setQCodeExecutor(QCodeExecutor qCodeExecutor)
|
||||
{
|
||||
this.qCodeExecutor = qCodeExecutor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void convertObject(String name, Object inputObject)
|
||||
{
|
||||
convertedObjectMap.put(name, qCodeExecutor.convertObjectToJava(inputObject));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Object getConvertedObject(String name)
|
||||
{
|
||||
return (convertedObjectMap.get(name));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -32,19 +32,24 @@ import com.kingsrook.qqq.api.actions.QRecordApiAdapter;
|
||||
import com.kingsrook.qqq.api.model.APIVersion;
|
||||
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaData;
|
||||
import com.kingsrook.qqq.api.model.metadata.ApiInstanceMetaDataContainer;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.QCodeExecutor;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.QCodeExecutorAware;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Object injected into script context, for interfacing with a QQQ API.
|
||||
*******************************************************************************/
|
||||
public class ApiScriptUtils implements Serializable
|
||||
public class ApiScriptUtils implements QCodeExecutorAware, Serializable
|
||||
{
|
||||
private String apiName;
|
||||
private String apiVersion;
|
||||
|
||||
private QCodeExecutor qCodeExecutor;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -165,6 +170,7 @@ public class ApiScriptUtils implements Serializable
|
||||
public Map<String, Serializable> insert(String tableApiName, Object body) throws QException
|
||||
{
|
||||
validateApiNameAndVersion("insert(" + tableApiName + ")");
|
||||
body = processBodyToJsonString(body);
|
||||
return (ApiImplementation.insert(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(body)));
|
||||
}
|
||||
|
||||
@ -176,6 +182,7 @@ public class ApiScriptUtils implements Serializable
|
||||
public List<Map<String, Serializable>> bulkInsert(String tableApiName, Object body) throws QException
|
||||
{
|
||||
validateApiNameAndVersion("bulkInsert(" + tableApiName + ")");
|
||||
body = processBodyToJsonString(body);
|
||||
return (ApiImplementation.bulkInsert(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(body)));
|
||||
}
|
||||
|
||||
@ -187,17 +194,61 @@ public class ApiScriptUtils implements Serializable
|
||||
public void update(String tableApiName, Object primaryKey, Object body) throws QException
|
||||
{
|
||||
validateApiNameAndVersion("update(" + tableApiName + "," + primaryKey + ")");
|
||||
body = processBodyToJsonString(body);
|
||||
ApiImplementation.update(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(primaryKey), String.valueOf(body));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Take a "body" object, which maybe defined in the script's language/run-time,
|
||||
** and try to process it into a JSON String (which is what the API Implementation wants)
|
||||
*******************************************************************************/
|
||||
private Object processBodyToJsonString(Object body)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the caller already supplied the object as a string, then return that string. //
|
||||
// and in case it can't be parsed as json, well, let that error come out of the api implementation, and go back to the caller. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(body instanceof String)
|
||||
{
|
||||
return (body);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the input body wasn't a json string, try to convert it from a language-type object (e.g., javscript) to a java-object, //
|
||||
// then make JSON out of that for the APIImplementation //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Object bodyJavaObject = processInputObjectViaCodeExecutor(body);
|
||||
return JsonUtils.toJson(bodyJavaObject);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Use the QCodeExecutor (if we have one) to process an input object from the
|
||||
** script's language into a (more) native java object.
|
||||
** e.g., a Nashorn ScriptObjectMirror will end up as a "primitive", or a List or Map of such
|
||||
*******************************************************************************/
|
||||
private Object processInputObjectViaCodeExecutor(Object body)
|
||||
{
|
||||
if(qCodeExecutor == null || body == null)
|
||||
{
|
||||
return (body);
|
||||
}
|
||||
|
||||
return (qCodeExecutor.convertObjectToJava(body));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<Map<String, Serializable>> bulkUpdate(String tableApiName, Object body) throws QException
|
||||
{
|
||||
validateApiNameAndVersion("bulkUpdate(" + tableApiName + ")");
|
||||
body = processBodyToJsonString(body);
|
||||
return (ApiImplementation.bulkUpdate(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(body)));
|
||||
}
|
||||
|
||||
@ -220,6 +271,7 @@ public class ApiScriptUtils implements Serializable
|
||||
public List<Map<String, Serializable>> bulkDelete(String tableApiName, Object body) throws QException
|
||||
{
|
||||
validateApiNameAndVersion("bulkDelete(" + tableApiName + ")");
|
||||
body = processBodyToJsonString(body);
|
||||
return (ApiImplementation.bulkDelete(getApiInstanceMetaData(), apiVersion, tableApiName, String.valueOf(body)));
|
||||
}
|
||||
|
||||
@ -257,4 +309,15 @@ public class ApiScriptUtils implements Serializable
|
||||
}
|
||||
return paramMap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setQCodeExecutor(QCodeExecutor qCodeExecutor)
|
||||
{
|
||||
this.qCodeExecutor = qCodeExecutor;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user