diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunner.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunner.java index ba1118a4..5351b53a 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunner.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/automation/polling/PollingAutomationPerTableRunner.java @@ -649,7 +649,7 @@ public class PollingAutomationPerTableRunner implements Runnable input.setRecordList(records); input.setAction(action); - RecordAutomationHandler recordAutomationHandler = QCodeLoader.getRecordAutomationHandler(action); + RecordAutomationHandler recordAutomationHandler = QCodeLoader.getAdHoc(RecordAutomationHandler.class, action.getCodeReference()); recordAutomationHandler.execute(input); } } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java index b4d46ca7..82466d65 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoader.java @@ -24,17 +24,11 @@ package com.kingsrook.qqq.backend.core.actions.customizers; import java.lang.reflect.Constructor; import java.util.Optional; -import java.util.function.Function; -import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler; -import com.kingsrook.qqq.backend.core.actions.processes.BackendStep; -import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider; -import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.logging.QLogger; +import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference; 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.model.metadata.possiblevalues.QPossibleValueSource; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; -import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction; import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction; import com.kingsrook.qqq.backend.core.utils.memoization.Memoization; import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; @@ -43,9 +37,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair; /******************************************************************************* ** Utility to load code for running QQQ customizers. ** - ** TODO - redo all to go through method that memoizes class & constructor - ** lookup. That memoziation causes 1,000,000 such calls to go from ~500ms - ** to ~100ms. + ** That memoization causes 1,000,000 such calls to go from ~500ms to ~100ms. *******************************************************************************/ public class QCodeLoader { @@ -70,84 +62,6 @@ public class QCodeLoader - /******************************************************************************* - ** - *******************************************************************************/ - @SuppressWarnings("unchecked") - public static Function getFunction(QCodeReference codeReference) - { - if(codeReference == null) - { - return (null); - } - - if(!codeReference.getCodeType().equals(QCodeType.JAVA)) - { - /////////////////////////////////////////////////////////////////////////////////////// - // todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! // - /////////////////////////////////////////////////////////////////////////////////////// - throw (new IllegalArgumentException("Only JAVA customizers are supported at this time.")); - } - - try - { - Class customizerClass = Class.forName(codeReference.getName()); - return ((Function) customizerClass.getConstructor().newInstance()); - } - catch(Exception e) - { - LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference)); - - ////////////////////////////////////////////////////////////////////////////////////////////////////////// - // return null here - under the assumption that during normal run-time operations, we'll never hit here // - // as we'll want to validate all functions in the instance validator at startup time (and IT will throw // - // if it finds an invalid code reference // - ////////////////////////////////////////////////////////////////////////////////////////////////////////// - return (null); - } - } - - - - /******************************************************************************* - ** - *******************************************************************************/ - @SuppressWarnings("unchecked") - public static T getBackendStep(Class expectedType, QCodeReference codeReference) - { - if(codeReference == null) - { - return (null); - } - - if(!codeReference.getCodeType().equals(QCodeType.JAVA)) - { - /////////////////////////////////////////////////////////////////////////////////////// - // todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! // - /////////////////////////////////////////////////////////////////////////////////////// - throw (new IllegalArgumentException("Only JAVA BackendSteps are supported at this time.")); - } - - try - { - Class customizerClass = Class.forName(codeReference.getName()); - return ((T) customizerClass.getConstructor().newInstance()); - } - catch(Exception e) - { - LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference)); - - ////////////////////////////////////////////////////////////////////////////////////////////////////////// - // return null here - under the assumption that during normal run-time operations, we'll never hit here // - // as we'll want to validate all functions in the instance validator at startup time (and IT will throw // - // if it finds an invalid code reference // - ////////////////////////////////////////////////////////////////////////////////////////////////////////// - return (null); - } - } - - - /******************************************************************************* ** *******************************************************************************/ @@ -177,7 +91,17 @@ public class QCodeLoader if(constructor.isPresent()) { - return ((T) constructor.get().newInstance()); + T t = (T) constructor.get().newInstance(); + + //////////////////////////////////////////////////////////////// + // if the object is initializable, then, well, initialize it! // + //////////////////////////////////////////////////////////////// + if(t instanceof InitializableViaCodeReference initializableViaCodeReference) + { + initializableViaCodeReference.initialize(codeReference); + } + + return t; } else { @@ -187,7 +111,7 @@ public class QCodeLoader } catch(Exception e) { - LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference)); + LOG.error("Error initializing codeReference", e, logPair("codeReference", codeReference)); ////////////////////////////////////////////////////////////////////////////////////////////////////////// // return null here - under the assumption that during normal run-time operations, we'll never hit here // @@ -198,67 +122,4 @@ public class QCodeLoader } } - - - /******************************************************************************* - ** - *******************************************************************************/ - public static RecordAutomationHandler getRecordAutomationHandler(TableAutomationAction action) throws QException - { - try - { - QCodeReference codeReference = action.getCodeReference(); - if(!codeReference.getCodeType().equals(QCodeType.JAVA)) - { - /////////////////////////////////////////////////////////////////////////////////////// - // todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! // - /////////////////////////////////////////////////////////////////////////////////////// - throw (new IllegalArgumentException("Only JAVA customizers are supported at this time.")); - } - - Class codeClass = Class.forName(codeReference.getName()); - Object codeObject = codeClass.getConstructor().newInstance(); - if(!(codeObject instanceof RecordAutomationHandler recordAutomationHandler)) - { - throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of RecordAutomationHandler")); - } - return (recordAutomationHandler); - } - catch(QException qe) - { - throw (qe); - } - catch(Exception e) - { - throw (new QException("Error getting record automation handler for action [" + action.getName() + "]", e)); - } - } - - - - /******************************************************************************* - ** - *******************************************************************************/ - public static QCustomPossibleValueProvider getCustomPossibleValueProvider(QPossibleValueSource possibleValueSource) throws QException - { - try - { - Class codeClass = Class.forName(possibleValueSource.getCustomCodeReference().getName()); - Object codeObject = codeClass.getConstructor().newInstance(); - if(!(codeObject instanceof QCustomPossibleValueProvider customPossibleValueProvider)) - { - throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of QCustomPossibleValueProvider")); - } - return (customPossibleValueProvider); - } - catch(QException qe) - { - throw (qe); - } - catch(Exception e) - { - throw (new QException("Error getting custom possible value provider for PVS [" + possibleValueSource.getName() + "]", e)); - } - } - } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java index eacc31ec..f032b609 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/reporting/GenerateReportAction.java @@ -222,7 +222,8 @@ public class GenerateReportAction extends AbstractQActionFunction viewCustomizerFunction = QCodeLoader.getFunction(dataSourceTableView.getViewCustomizer()); + @SuppressWarnings("unchecked") + Function viewCustomizerFunction = QCodeLoader.getAdHoc(Function.class, dataSourceTableView.getViewCustomizer()); if(viewCustomizerFunction instanceof ReportViewCustomizer reportViewCustomizer) { reportViewCustomizer.setReportInput(reportInput); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QJavaExecutor.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QJavaExecutor.java index 61451b38..ae583279 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QJavaExecutor.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/scripts/QJavaExecutor.java @@ -53,7 +53,8 @@ public class QJavaExecutor implements QCodeExecutor Serializable output; try { - Function, Serializable> function = QCodeLoader.getFunction(codeReference); + @SuppressWarnings("unchecked") + Function, Serializable> function = QCodeLoader.getAdHoc(Function.class, codeReference); output = function.apply(context); } catch(Exception e) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java index 80eeb6a7..1aa7adf5 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/QPossibleValueTranslator.java @@ -341,7 +341,7 @@ public class QPossibleValueTranslator try { - QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource); + QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference()); return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value))); } catch(Exception e) diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java index 6416ff50..42ddb559 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/values/SearchPossibleValueSourceAction.java @@ -424,7 +424,7 @@ public class SearchPossibleValueSourceAction { try { - QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource); + QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference()); List> possibleValues = customPossibleValueProvider.search(input); SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput(); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java index 14dda867..eb54ff6b 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/instances/QInstanceEnricher.java @@ -1425,7 +1425,7 @@ public class QInstanceEnricher { try { - QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource); + QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference()); Method getPossibleValueMethod = customPossibleValueProvider.getClass().getDeclaredMethod("getPossibleValue", Serializable.class); Type returnType = getPossibleValueMethod.getGenericReturnType(); diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/InitializableViaCodeReference.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/InitializableViaCodeReference.java new file mode 100644 index 00000000..976231c3 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/InitializableViaCodeReference.java @@ -0,0 +1,38 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. 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.backend.core.model.metadata.code; + + +/******************************************************************************* + ** an object which is intended to be constructed via a CodeReference, and, + ** moreso, after it is created, then the initialize method here gets called, + ** passing the codeRefernce in - e.g., to do additional initalization of the + ** object, e.g., properties in a QCodeReferenceWithProperties + *******************************************************************************/ +public interface InitializableViaCodeReference +{ + /*************************************************************************** + ** + ***************************************************************************/ + void initialize(QCodeReference codeReference); + +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/QCodeReference.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/QCodeReference.java index 5c475a4e..5beba2ea 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/QCodeReference.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/QCodeReference.java @@ -29,7 +29,7 @@ import java.io.Serializable; ** Pointer to code to be ran by the qqq framework, e.g., for custom behavior - ** maybe process steps, maybe customization to a table, etc. *******************************************************************************/ -public class QCodeReference implements Serializable +public class QCodeReference implements Serializable, Cloneable { private String name; private QCodeType codeType; @@ -58,6 +58,25 @@ public class QCodeReference implements Serializable + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public QCodeReference clone() + { + try + { + QCodeReference clone = (QCodeReference) super.clone(); + return clone; + } + catch(CloneNotSupportedException e) + { + throw new AssertionError(); + } + } + + + /******************************************************************************* ** *******************************************************************************/ @@ -179,5 +198,4 @@ public class QCodeReference implements Serializable this.inlineCode = inlineCode; return (this); } - } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/QCodeReferenceWithProperties.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/QCodeReferenceWithProperties.java new file mode 100644 index 00000000..271127f3 --- /dev/null +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/code/QCodeReferenceWithProperties.java @@ -0,0 +1,59 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. 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.backend.core.model.metadata.code; + + +import java.io.Serializable; +import java.util.Map; + + +/******************************************************************************* + ** a code reference that also has a map of properties. This object (with the + ** properties) will be passed in to the referenced object, if it implements + ** InitializableViaCodeReference. + *******************************************************************************/ +public class QCodeReferenceWithProperties extends QCodeReference +{ + private final Map properties; + + + + /*************************************************************************** + ** + ***************************************************************************/ + public QCodeReferenceWithProperties(Class javaClass, Map properties) + { + super(javaClass); + this.properties = properties; + } + + + + /******************************************************************************* + ** Getter for properties + ** + *******************************************************************************/ + public Map getProperties() + { + return properties; + } +} diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/nocode/WidgetAdHocValue.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/nocode/WidgetAdHocValue.java index 384dd684..88151665 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/nocode/WidgetAdHocValue.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/model/metadata/dashboard/nocode/WidgetAdHocValue.java @@ -62,7 +62,8 @@ public class WidgetAdHocValue extends AbstractWidgetValueSource context.putAll(inputValues); } - Function function = QCodeLoader.getFunction(codeReference); + @SuppressWarnings("unchecked") + Function function = QCodeLoader.getAdHoc(Function.class, codeReference); Object result = function.apply(context); return (result); } diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/BaseStreamedETLStep.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/BaseStreamedETLStep.java index 728d78f3..3b006bc5 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/BaseStreamedETLStep.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/processes/implementations/etl/streamedwithfrontend/BaseStreamedETLStep.java @@ -53,7 +53,7 @@ public class BaseStreamedETLStep protected AbstractExtractStep getExtractStep(RunBackendStepInput runBackendStepInput) { QCodeReference codeReference = (QCodeReference) runBackendStepInput.getValue(StreamedETLWithFrontendProcess.FIELD_EXTRACT_CODE); - return (QCodeLoader.getBackendStep(AbstractExtractStep.class, codeReference)); + return (QCodeLoader.getAdHoc(AbstractExtractStep.class, codeReference)); } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoaderTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoaderTest.java index 83cf8298..9c9d56a6 100644 --- a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoaderTest.java +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/actions/customizers/QCodeLoaderTest.java @@ -22,12 +22,19 @@ package com.kingsrook.qqq.backend.core.actions.customizers; +import java.util.Map; import com.kingsrook.qqq.backend.core.BaseTest; +import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference; import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference; +import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceWithProperties; +import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.Timer; +import com.kingsrook.qqq.backend.core.utils.ValueUtils; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; /******************************************************************************* @@ -80,6 +87,7 @@ class QCodeLoaderTest extends BaseTest } + /******************************************************************************* ** *******************************************************************************/ @@ -91,4 +99,50 @@ class QCodeLoaderTest extends BaseTest } } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testCodeReferenceWithProperties() + { + assertNull(QCodeLoader.getAdHoc(SomeClass.class, new QCodeReference(SomeClass.class))); + + SomeClass someObject = QCodeLoader.getAdHoc(SomeClass.class, new QCodeReferenceWithProperties(SomeClass.class, Map.of("property", "someValue"))); + assertEquals("someValue", someObject.someProperty); + + SomeClass someOtherObject = QCodeLoader.getAdHoc(SomeClass.class, new QCodeReferenceWithProperties(SomeClass.class, Map.of("property", "someOtherValue"))); + assertEquals("someOtherValue", someOtherObject.someProperty); + } + + + + /*************************************************************************** + ** + ***************************************************************************/ + public static class SomeClass implements InitializableViaCodeReference + { + private String someProperty; + + + + /*************************************************************************** + ** + ***************************************************************************/ + @Override + public void initialize(QCodeReference codeReference) + { + if(codeReference instanceof QCodeReferenceWithProperties codeReferenceWithProperties) + { + someProperty = ValueUtils.getValueAsString(codeReferenceWithProperties.getProperties().get("property")); + } + + if(!StringUtils.hasContent(someProperty)) + { + throw new IllegalStateException("Missing property"); + } + } + } + } \ No newline at end of file