Compare commits

..

1 Commits

Author SHA1 Message Date
1599313b75 Bump org.apache.poi:poi-ooxml
Bumps the maven group with 1 update in the /qqq-backend-core directory: org.apache.poi:poi-ooxml.


Updates `org.apache.poi:poi-ooxml` from 5.2.5 to 5.4.0

---
updated-dependencies:
- dependency-name: org.apache.poi:poi-ooxml
  dependency-version: 5.4.0
  dependency-type: direct:production
  dependency-group: maven
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-10 15:23:37 +00:00
382 changed files with 1165 additions and 22651 deletions

View File

@ -1,26 +0,0 @@
#!/bin/bash
############################################################################
## Script to collect all JaCoCo reports from different modules into a
## single directory for easier artifact storage in CI.
############################################################################
mkdir -p /home/circleci/jacoco-reports/
##############################################################
## Find all module directories that have target/site/jacoco ##
##############################################################
for module_dir in */; do
if [ -d "${module_dir}target/site/jacoco" ]; then
module_name=$(basename "${module_dir%/}")
target_dir="/home/circleci/jacoco-reports/${module_name}"
echo "Collecting JaCoCo reports for module: ${module_name}"
cp -r "${module_dir}target/site/jacoco" "${target_dir}"
echo "Copied JaCoCo reports for ${module_name} to ${target_dir}"
fi
done
echo "All JaCoCo reports collected to /home/circleci/jacoco-reports/"

View File

@ -1,48 +0,0 @@
#!/bin/bash
############################################################################
## Script to concatenate all .txt files in the surefire-reports directory
## into a single artifact that can be stored in CI.
############################################################################
mkdir -p /home/circleci/test-output-artifacts/
###################################################################
## Find all module directories that have target/surefire-reports ##
###################################################################
for module_dir in */; do
if [ -d "${module_dir}target/surefire-reports" ]; then
module_name=$(basename "${module_dir%/}")
output_file="/home/circleci/test-output-artifacts/${module_name}-test-output.txt"
echo "Processing module: ${module_name}"
echo "Output file: ${output_file}"
##################################################################
## Concatenate all .txt files in the surefire-reports directory ##
##################################################################
if [ -n "$(find "${module_dir}target/surefire-reports" -name "*.txt" -type f)" ]; then
echo "=== Test Output for ${module_name} ===" > "${output_file}"
echo "Generated at: $(date)" >> "${output_file}"
echo "==========================================" >> "${output_file}"
echo "" >> "${output_file}"
##############################################
## Sort files to ensure consistent ordering ##
##############################################
find "${module_dir}target/surefire-reports" -name "*.txt" -type f | sort | while read -r txt_file; do
echo "--- File: $(basename "${txt_file}") ---" >> "${output_file}"
cat "${txt_file}" >> "${output_file}"
echo "" >> "${output_file}"
echo "--- End of $(basename "${txt_file}") ---" >> "${output_file}"
echo "" >> "${output_file}"
echo "" >> "${output_file}"
echo "" >> "${output_file}"
done
echo "Concatenated test output for ${module_name} to ${output_file}"
else
echo "No .txt files found in ${module_dir}target/surefire-reports"
fi
fi
done

View File

@ -5,7 +5,35 @@ orbs:
browser-tools: circleci/browser-tools@1.4.7
commands:
mvn_build:
store_jacoco_site:
parameters:
module:
type: string
steps:
- store_artifacts:
path: << parameters.module >>/target/site/jacoco/index.html
when: always
- store_artifacts:
path: << parameters.module >>/target/site/jacoco/jacoco-resources
when: always
install_java17:
steps:
- run:
name: Install Java 17
command: |
sudo apt-get update
sudo apt install -y openjdk-17-jdk
sudo rm /etc/alternatives/java
sudo ln -s /usr/lib/jvm/java-17-openjdk-amd64/bin/java /etc/alternatives/java
- run:
## used by jacoco uncovered class reporting in pom.xml
name: Install html2text
command: |
sudo apt-get update
sudo apt-get install -y html2text
mvn_verify:
steps:
- checkout
- restore_cache:
@ -17,41 +45,30 @@ commands:
name: Write .env
command: |
echo "RDBMS_PASSWORD=$RDBMS_PASSWORD" >> qqq-sample-project/.env
- run:
name: Run Maven Compile
command: |
mvn -s .circleci/mvn-settings.xml -T4 --no-transfer-progress compile
- save_cache:
paths:
- ~/.m2
key: v1-dependencies-{{ checksum "pom.xml" }}
mvn_verify:
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "pom.xml" }}
- run:
name: Run Maven Verify
command: |
mvn -s .circleci/mvn-settings.xml -T4 --no-transfer-progress verify
- run:
name: Collect JaCoCo reports
command: .circleci/collect-jacoco-reports.sh
when: always
- store_artifacts:
path: /home/circleci/jacoco-reports
destination: jacoco-reports
when: always
- run:
name: Concatenate test output files
command: .circleci/concatenate-test-output.sh
when: always
- store_artifacts:
path: /home/circleci/test-output-artifacts
destination: test-output
when: always
mvn -s .circleci/mvn-settings.xml -T4 verify
- store_jacoco_site:
module: qqq-backend-core
- store_jacoco_site:
module: qqq-backend-module-filesystem
- store_jacoco_site:
module: qqq-backend-module-rdbms
- store_jacoco_site:
module: qqq-backend-module-api
- store_jacoco_site:
module: qqq-middleware-api
- store_jacoco_site:
module: qqq-middleware-javalin
- store_jacoco_site:
module: qqq-middleware-picocli
- store_jacoco_site:
module: qqq-middleware-slack
- store_jacoco_site:
module: qqq-language-support-javascript
- store_jacoco_site:
module: qqq-sample-project
- run:
name: Save test results
command: |
@ -60,6 +77,10 @@ commands:
when: always
- store_test_results:
path: ~/test-results
- save_cache:
paths:
- ~/.m2
key: v1-dependencies-{{ checksum "pom.xml" }}
check_middleware_api_versions:
steps:
@ -70,8 +91,8 @@ commands:
- run:
name: Build and Run ValidateApiVersions
command: |
mvn -s .circleci/mvn-settings.xml -T4 --no-transfer-progress install -DskipTests
mvn -s .circleci/mvn-settings.xml -T4 --no-transfer-progress -pl qqq-middleware-javalin package appassembler:assemble -DskipTests
mvn -s .circleci/mvn-settings.xml -T4 install -DskipTests
mvn -s .circleci/mvn-settings.xml -pl qqq-middleware-javalin package appassembler:assemble -DskipTests
qqq-middleware-javalin/target/appassembler/bin/ValidateApiVersions -r $(pwd)
mvn_jar_deploy:
@ -87,7 +108,7 @@ commands:
- run:
name: Run Maven Jar Deploy
command: |
mvn -s .circleci/mvn-settings.xml -T4 --no-transfer-progress flatten:flatten jar:jar deploy:deploy
mvn -s .circleci/mvn-settings.xml -T4 flatten:flatten jar:jar deploy:deploy
- save_cache:
paths:
- ~/.m2
@ -114,25 +135,19 @@ commands:
when: always
jobs:
build:
executor: localstack/default
steps:
- mvn_build
test:
mvn_test:
executor: localstack/default
steps:
## - localstack/startup
- install_java17
- mvn_verify
api_version_check:
executor: localstack/default
steps:
- check_middleware_api_versions
mvn_deploy:
executor: localstack/default
steps:
- mvn_build
## - localstack/startup
- install_java17
- mvn_verify
- check_middleware_api_versions
- mvn_jar_deploy
@ -146,31 +161,13 @@ jobs:
workflows:
test_only:
jobs:
- build:
- mvn_test:
context: [ qqq-maven-registry-credentials, build-qqq-sample-app ]
filters:
branches:
ignore: /(dev|integration.*)/
tags:
ignore: /(version|snapshot)-.*/
- test:
context: [ qqq-maven-registry-credentials, build-qqq-sample-app ]
requires:
- build
filters:
branches:
ignore: /(dev|integration.*)/
tags:
ignore: /(version|snapshot)-.*/
- api_version_check:
context: [ qqq-maven-registry-credentials, build-qqq-sample-app ]
requires:
- build
filters:
branches:
ignore: /(dev|integration.*)/
tags:
ignore: /(version|snapshot)-.*/
deploy:
jobs:

View File

@ -30,20 +30,6 @@ There are a few useful IntelliJ settings files, under `qqq-dev-tools/intellij`:
One will likely also want the [Kingsrook Commentator
Plugin](https://plugins.jetbrains.com/plugin/19325-kingsrook-commentator).
## Test Logging
By default, when ran from the command line, mvn surefire will make each test's
output (e.g., System.out, err, printStackTrace, and all logger calls) go into a
file under target/surefire-reports/${className}.txt.
The system property `-DtestOutputToFile=false` can be given on the command line
to get all of this output on the console.
In the IDE (e.g,. IntelliJ), output goes to the Console.
In CircleCI, output goes to files, and those files are concatenated together and
stored as artifacts.
## License
QQQ - Low-code Application Framework for Engineers. \
Copyright (C) 2020-2024. Kingsrook, LLC \

View File

@ -91,7 +91,7 @@ And then having a bug in the check permission logic on the _Light Bulb Inventory
No!
All of the (really important, even though application developers hate doing it) aspects of security - you don't need to write ANY code for dealing with that.
Just tell QQQ what Authentication provider you want to use (e.g., OAuth2 or https://auth0.com/[Auth0]), and - to paraphrase the old https://www.youtube.com/watch?v=YHzM4avGrKI[iMac ad] - there's no step 2.
Just tell QQQ what Authentication provider you want to use (e.g., https://auth0.com/[Auth0]), and - to paraphrase the old https://www.youtube.com/watch?v=YHzM4avGrKI[iMac ad] - there's no step 2.
QQQ just does it.
''''

View File

@ -31,9 +31,11 @@ include::metaData/PermissionRules.adoc[leveloffset=+1]
== Services
include::misc/Javalin.adoc[leveloffset=+1]
include::misc/ScheduledJobs.adoc[leveloffset=+1]
=== Web server (Javalin)
#todo#
=== API server (OpenAPI)
#todo#

View File

@ -1,109 +0,0 @@
== QQQ Middleware: Javalin web server
include::../variables.adoc[]
QQQ provides a standard implementation of a middleware layer - that is - code that exists between the
QQQ backend and user interface. This implementation is a web server built using the https://javalin.io/[Javalin framework],
packaged and deployed in the `qqq-middleware-javalin` maven module
The de facto way to create a QQQ application server is to write a class which uses an instance of one of the
subclasses of `QApplicationJavalinServer`.
For example, if your application metadata is defined in a directory of yaml files, your server class could be implemented as:
[source,java]
.ConfigFileBasedQQQApplication usage example
----
public static void main(String[] args)
{
try
{
String path = "src/main/resources/metadata";
ConfigFilesBasedQQQApplication application = new ConfigFilesBasedQQQApplication(path);
QApplicationJavalinServer javalinServer = new QApplicationJavalinServer(application);
javalinServer.start();
}
catch(Exception e)
{
LOG.error("Failed to start javalin server. See stack trace for details.", e);
}
}
----
A similar class exists if your metadata is produced by a package of Java MetaDataProducer objects: `MetaDataProducerBasedQQQApplication`.
=== QApplicationJavalinServer
This class provides the bridge between your QQQ Application (e.g., your metadata) and the QQQ Middleware layer
served by a Javalin web server. It has several properties to control behaviors:
* `Integer port` - (default `8000`) - port to use for serving HTTP.
* `boolean serveFrontendMaterialDashboard` - (default `true`) whether to serve the javascript frontend provided
in the maven artifact `qqq-frontend-material-dashboard`.
* `boolean serveLegacyUnversionedMiddlewareAPI` - (default `true`) whether to serve a version the original implementation
of the QQQ middleware, which current version of `qqq-frontend-material-dashboard` are compatible with.
* `List<AbstractMiddlewareVersion> middlewareVersionList` - (default contains `MiddlewareVersionV1`) - list of
newer, formally versioned implementations of the QQQ middleware interface to be served.
* `Consumer<Javalin> javalinConfigurationCustomizer` - (default `null`) - optional hook to customize the
javalin service object before it is started.
* `List<QJavalinRouteProviderInterface> additionalRouteProviders` - (default `null`) - list of fully custom
implementations of `QJavalinRouteProviderInterface`, to add additional endpoints to the javalin server.
** _Note, you may first want to consider using JavalinRouteProviderMetaData instead - see below._
* `QJavalinMetaData javalinMetaData` - (default `null`) - optional alternative place to define `JavalinMetaData` (vs.
defining it in the `QInstance`). _Note that if it is set in both places, the one in the QApplicationJavalinServer
is used._
=== JavalinMetaData
Certain behaviors of a QQQ Javalin server are configured in a declarative manner by adding a `QJavalinMetaData`
object to the `supplementalMetaData` in your `QInstance` (or, as mentioned above, by setting it directly on the
`QApplicationJavalinServer`):
* `List<JavalinRouteProviderMetaData> routeProviders` - (default `null`) optional list of custom route providers to
add to the Javalin server. See below for details.
* `String uploadedFileArchiveTableName` - (default `null`) - reference to a QQQ Table in your application instance,
needed to support the Bulk Load process, as well as any other processes which need to accept an uploaded file
as input.
* `boolean loggerDisabled` - (default `false`)
* `Function<QJavalinAccessLogger.LogEntry, Boolean> logFilter` - (default `null`)
* `boolean queryWithoutLimitAllowed` - (default `false`)
* `Integer queryWithoutLimitDefault` - (default `1000`)
* `Level queryWithoutLimitLogLevel` - (default `INFO`)
==== JavalinRouteProviderMetaData
This type of metadata allows you to add additional http route providers to your Javalin instance, e.g., for
serving static files or for running custom code from your application (in the form of QQQ Processes) to respond
to HTTP requests.
* `String hostedPath` - (required)
* `String fileSystemPath` - (required for a static router)
* `String processName` - required for a dynamic, process-based router. Must be a process name within the QQQ Instance.
See below for additional details
* `List<String> methods` - required list of HTTP methods (verbs) that are served by the route provider
* `QCodeReference routeAuthenticator - Optional reference to a class that implements `RouteAuthenticatorInterface`,
to provide security authentication to all requests handled by the route provider.
** A default implementation is provided as `SimpleRouteAuthenticator`, which requires that a user session be present
to access paths served by the route provider.
===== Process-based route provider processes
If you define a `JavalinRouteProviderMetaData` with a `processName` (e.g., to serve dynamic HTTP responses from your javalin
server), the process that you implement will be called to respond to any HTTP requests received by the javalin
server which match the `hostedPath` and `methods` that are specified in the metadata.
The QQQ javalin server will marshal request data from the javalin context into the process's payload, conforming to
the shape of the `ProcessBasedRouterPayload` class. Similarly, the http response will be built by taking values from
the process's output/state conforming to the fields in that class. As such, it is recommended to use a
`ProcessBasedRouterPayload` instance, as show in this example:
[source,java]
.Process-based router usage example (including ProcessBasedRouterPayload)
----
public class MyDynamicSiteProcessStep implements BackendStep
{
@Override
public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
{
ProcessBasedRouterPayload payload = input.getProcessPayload(ProcessBasedRouterPayload.class);
String path = payload.getPath();
payload.setResponseString("You requested: " + path);
output.setProcessPayload(payload);
}
}
----

34
pom.xml
View File

@ -48,7 +48,7 @@
</modules>
<properties>
<revision>0.26.0-SNAPSHOT</revision>
<revision>0.25.0-SNAPSHOT</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
@ -59,7 +59,6 @@
<coverage.instructionCoveredRatioMinimum>0.80</coverage.instructionCoveredRatioMinimum>
<coverage.classCoveredRatioMinimum>0.95</coverage.classCoveredRatioMinimum>
<plugin.shade.phase>none</plugin.shade.phase>
<testOutputToFile>true</testOutputToFile>
</properties>
<profiles>
@ -142,8 +141,6 @@
<configuration>
<!-- Sets the VM argument line used when integration tests are run. -->
<argLine>@{jaCoCoArgLine}</argLine>
<!-- Reduce console output for cleaner JUnit output -->
<redirectTestOutputToFile>${testOutputToFile}</redirectTestOutputToFile>
</configuration>
</plugin>
<plugin>
@ -247,29 +244,30 @@ if [ ! -e target/site/jacoco/index.html ]; then
fi
echo
echo "Jacoco coverage summary report for module: ${project.artifactId}"
echo "Jacoco coverage summary report:"
echo " See also target/site/jacoco/index.html"
echo " and https://www.jacoco.org/jacoco/trunk/doc/counters.html"
echo "------------------------------------------------------------"
# Parse Jacoco HTML coverage summary
if [ -f target/site/jacoco/index.html ]; then
echo -e "Instructions Missed\nInstruction Coverage\nBranches Missed\nBranch Coverage\nComplexity Missed\nComplexity Hit\nLines Missed\nLines Hit\nMethods Missed\nMethods Hit\nClasses Missed\nClasses Hit\n" > /tmp/$$.headers
sed 's/<\/\w\+>/&\n/g' target/site/jacoco/index.html | grep -A 12 '<tfoot>' | grep '<td' | sed 's/<td class="\w\+\d*">\([^<]*\)<\/td>/\1/' | grep -v Total > /tmp/$$.values
if which xpath > /dev/null 2>&1; then
echo "Element\nInstructions Missed\nInstruction Coverage\nBranches Missed\nBranch Coverage\nComplexity Missed\nComplexity Hit\nLines Missed\nLines Hit\nMethods Missed\nMethods Hit\nClasses Missed\nClasses Hit\n" > /tmp/$$.headers
xpath -n -q -e '/html/body/table/tfoot/tr[1]/td/text()' target/site/jacoco/index.html > /tmp/$$.values
paste /tmp/$$.headers /tmp/$$.values | tail +2 | awk -v FS='\t' '{printf("%-20s %s\n",$1,$2)}'
rm /tmp/$$.headers /tmp/$$.values
else
echo "Jacoco coverage summary was not found.";
echo "xpath is not installed. Jacoco coverage summary will not be produced here...";
fi
echo "-----------------------------"
echo
echo "Untested classes, per Jacoco for module: ${project.artifactId}"
echo "-----------------------------"
# Parse Jacoco XML reports directly to find classes with 0% coverage
sed 's/<classs .*\?>/&\n/g;s/<\/class>/&\n/g' target/site/jacoco/jacoco.xml | grep -v 'counter type="CLASS" missed="0"' | sed 's/>.*//;s/.*\///;s/".*//'
echo "-----------------------------"
echo
if which html2text > /dev/null 2>&1; then
echo "Untested classes, per Jacoco:"
echo "-----------------------------"
for i in target/site/jacoco/*/index.html; do
html2text -width 500 -nobs $i | sed '1,/^Total/d;' | grep -v Created | sed 's/ \+/ /g' | sed 's/ [[:digit:]]$//' | grep -v 0$ | cut -d' ' -f1;
done;
echo
else
echo "html2text is not installed. Untested classes from Jacoco will not be printed here...";
fi
]]>
</argument>

View File

@ -119,12 +119,7 @@
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.25.0</version>
<version>5.4.0</version>
</dependency>
<!-- adding to help FastExcel -->
@ -134,16 +129,10 @@
<version>2.16.0</version>
</dependency>
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>oauth2-oidc-sdk</artifactId>
<version>11.23.1</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>auth0</artifactId>
<version>2.18.0</version>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
@ -153,12 +142,12 @@
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.22.1</version>
<version>0.22.0</version>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.2.0</version>
<artifactId>java-dotenv</artifactId>
<version>5.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>

View File

@ -291,7 +291,6 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
/////////////////////////////
InsertInput insertInput = new InsertInput();
insertInput.setTableName("audit");
insertInput.setTransaction(input.getTransaction());
insertInput.setRecords(auditRecords);
InsertOutput insertOutput = new InsertAction().execute(insertInput);
@ -319,7 +318,6 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
{
insertInput = new InsertInput();
insertInput.setTableName("auditDetail");
insertInput.setTransaction(input.getTransaction());
insertInput.setRecords(auditDetailRecords);
new InsertAction().execute(insertInput);
}

View File

@ -124,7 +124,6 @@ public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAu
String contextSuffix = getContentSuffix(input);
AuditInput auditInput = new AuditInput();
auditInput.setTransaction(input.getTransaction());
if(auditLevel.equals(AuditLevel.RECORD) || (auditLevel.equals(AuditLevel.FIELD) && !dmlType.supportsFields))
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -1,38 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.automation;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
/*******************************************************************************
** interface to be implemented by one that wishes to execute custom table triggers
*******************************************************************************/
public interface CustomTableTriggerRecordAutomationHandler extends RecordAutomationHandlerInterface
{
/***************************************************************************
**
***************************************************************************/
boolean handlesThisInput(RecordAutomationInput recordAutomationInput) throws QException;
}

View File

@ -22,11 +22,19 @@
package com.kingsrook.qqq.backend.core.actions.automation;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
/*******************************************************************************
** Base class for custom-codes to run as an automation action
*******************************************************************************/
@Deprecated(since = "0.26.0 - when RecordAutomationHandlerInterface was introduced")
public abstract class RecordAutomationHandler implements RecordAutomationHandlerInterface
public abstract class RecordAutomationHandler
{
/*******************************************************************************
**
*******************************************************************************/
public abstract void execute(RecordAutomationInput recordAutomationInput) throws QException;
}

View File

@ -1,40 +0,0 @@
/*
* 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.backend.core.actions.automation;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
/*******************************************************************************
** Interface for custom-codes to run as an automation action
*******************************************************************************/
public interface RecordAutomationHandlerInterface
{
/*******************************************************************************
**
*******************************************************************************/
void execute(RecordAutomationInput recordAutomationInput) throws QException;
}

View File

@ -1,89 +0,0 @@
/*
* 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.automation;
import java.util.LinkedHashMap;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
** RecordAutomationHandler implementation that is called by automation runner
** that doesn't know to deal with a TableTrigger record that it received.
**
** e.g., if an app has altered that table (e.g., workflows-qbit).
*******************************************************************************/
public class RunCustomTableTriggerRecordAutomationHandler implements RecordAutomationHandlerInterface
{
private static final QLogger LOG = QLogger.getLogger(RunCustomTableTriggerRecordAutomationHandler.class);
private static Map<String, QCodeReference> handlers = new LinkedHashMap<>();
/***************************************************************************
**
***************************************************************************/
public static void registerHandler(String name, QCodeReference codeReference)
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if there's already a value mapped for this name, warn about it (unless it's for the same code reference) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(handlers.containsKey(name))
{
if(handlers.get(name).getName().equals(codeReference.getName()))
{
LOG.warn("Registering a CustomTableTriggerRecordAutomationHandler for a name that is already registered", logPair("name", name));
}
}
handlers.put(name, codeReference);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void execute(RecordAutomationInput recordAutomationInput) throws QException
{
for(QCodeReference codeReference : handlers.values())
{
CustomTableTriggerRecordAutomationHandler customHandler = QCodeLoader.getAdHoc(CustomTableTriggerRecordAutomationHandler.class, codeReference);
if(customHandler.handlesThisInput(recordAutomationInput))
{
customHandler.execute(recordAutomationInput);
return;
}
}
throw (new QException("No custom record automation handler was found for " + recordAutomationInput));
}
}

View File

@ -51,7 +51,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
**
*******************************************************************************/
public class RunRecordScriptAutomationHandler implements RecordAutomationHandlerInterface
public class RunRecordScriptAutomationHandler extends RecordAutomationHandler
{
private static final QLogger LOG = QLogger.getLogger(RunRecordScriptAutomationHandler.class);

View File

@ -33,9 +33,8 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandlerInterface;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
import com.kingsrook.qqq.backend.core.actions.automation.RunCustomTableTriggerRecordAutomationHandler;
import com.kingsrook.qqq.backend.core.actions.automation.RunRecordScriptAutomationHandler;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
@ -443,33 +442,15 @@ public class PollingAutomationPerTableRunner implements Runnable
}
}
TableAutomationAction tableAutomationAction = new TableAutomationAction()
rs.add(new TableAutomationAction()
.withName("Script:" + tableTrigger.getScriptId())
.withFilter(filter)
.withTriggerEvent(triggerEvent)
.withPriority(tableTrigger.getPriority())
.withIncludeRecordAssociations(true);
///////////////////////////////////////////////////////////////////////////////////////////////////////
// if the table trigger has a script id on it, then we know how to run that here in qqq-backend-core //
///////////////////////////////////////////////////////////////////////////////////////////////////////
if(tableTrigger.getScriptId() != null)
{
rs.add(tableAutomationAction
.withName("Script:" + tableTrigger.getScriptId())
.withValues(MapBuilder.of("scriptId", tableTrigger.getScriptId()))
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class)));
}
else
{
////////////////////////////////////////////////////////////////////////////////////////////////
// but - the app may have added an extension to the TableTrigger table (e.g., workflows qbit) //
// so, defer to RunCustomRecordAutomationHandler for unrecognized triggers //
////////////////////////////////////////////////////////////////////////////////////////////////
rs.add(tableAutomationAction
.withName("Custom Trigger:" + tableTrigger.getScriptId())
.withValues(MapBuilder.of("tableTriggerId", tableTrigger.getId()))
.withCodeReference(new QCodeReference(RunCustomTableTriggerRecordAutomationHandler.class)));
}
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class))
.withValues(MapBuilder.of("scriptId", tableTrigger.getScriptId()))
.withIncludeRecordAssociations(true)
);
}
catch(Exception e)
{
@ -545,7 +526,7 @@ public class PollingAutomationPerTableRunner implements Runnable
// note - this method - will re-query the objects, so we should have confidence that their data is fresh... //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
List<QRecord> matchingQRecords = getRecordsMatchingActionFilter(table, records, action);
LOG.debug("Of the [" + records.size() + "] records that were pending automations, [" + matchingQRecords.size() + "] of them match the filter on the action:" + action);
LOG.debug("Of the [" + records.size() + "] records that were pending automations, [" + matchingQRecords.size() + "] of them match the filter on the action:" + action);
if(CollectionUtils.nullSafeHasContents(matchingQRecords))
{
LOG.debug(" Processing " + matchingQRecords.size() + " records in " + table + " for action " + action);
@ -668,7 +649,7 @@ public class PollingAutomationPerTableRunner implements Runnable
input.setRecordList(records);
input.setAction(action);
RecordAutomationHandlerInterface recordAutomationHandler = QCodeLoader.getAdHoc(RecordAutomationHandlerInterface.class, action.getCodeReference());
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getAdHoc(RecordAutomationHandler.class, action.getCodeReference());
recordAutomationHandler.execute(input);
}
}

View File

@ -1,226 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.customizers;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
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.collections.MapBuilder;
/*******************************************************************************
** Implementation of TableCustomizerInterface that runs multiple other customizers
*******************************************************************************/
public class MultiCustomizer implements InitializableViaCodeReference, TableCustomizerInterface
{
private static final String KEY_CODE_REFERENCES = "codeReferences";
private List<TableCustomizerInterface> customizers = new ArrayList<>();
/***************************************************************************
* Factory method that builds a {@link QCodeReferenceWithProperties} that will
* allow this multi-customizer to be assigned to a table, and to track
* in that code ref's properties, the "sub" QCodeReferences to be used.
*
* Added to a table as in:
* <pre>
* table.withCustomizer(TableCustomizers.POST_INSERT_RECORD,
* MultiCustomizer.of(QCodeReference(x), QCodeReference(y)));
* </pre>
*
* @param codeReferences
* one or more {@link QCodeReference objects} to run when this customizer
* runs. note that they will run in the order provided in this list.
***************************************************************************/
public static QCodeReferenceWithProperties of(QCodeReference... codeReferences)
{
ArrayList<QCodeReference> list = new ArrayList<>(Arrays.stream(codeReferences).toList());
return (new QCodeReferenceWithProperties(MultiCustomizer.class, MapBuilder.of(KEY_CODE_REFERENCES, list)));
}
/***************************************************************************
* Add an additional table customizer code reference to an existing
* codeReference, e.g., constructed by the `of` factory method.
*
* @see #of(QCodeReference...)
***************************************************************************/
public static void addTableCustomizer(QCodeReferenceWithProperties existingMultiCustomizerCodeReference, QCodeReference codeReference)
{
ArrayList<QCodeReference> list = (ArrayList<QCodeReference>) existingMultiCustomizerCodeReference.getProperties().computeIfAbsent(KEY_CODE_REFERENCES, key -> new ArrayList<>());
list.add(codeReference);
}
/***************************************************************************
* When this class is instantiated by the QCodeLoader, initialize the
* sub-customizer objects.
***************************************************************************/
@Override
public void initialize(QCodeReference codeReference)
{
if(codeReference instanceof QCodeReferenceWithProperties codeReferenceWithProperties)
{
Serializable codeReferencesPropertyValue = codeReferenceWithProperties.getProperties().get(KEY_CODE_REFERENCES);
if(codeReferencesPropertyValue instanceof List<?> list)
{
for(Object o : list)
{
if(o instanceof QCodeReference reference)
{
TableCustomizerInterface customizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, reference);
customizers.add(customizer);
}
}
}
else
{
LOG.warn("Property KEY_CODE_REFERENCES [" + KEY_CODE_REFERENCES + "] must be a List<QCodeReference>.");
}
}
if(customizers.isEmpty())
{
LOG.info("No TableCustomizers were specified for MultiCustomizer.");
}
}
/***************************************************************************
* run postQuery method over all sub-customizers
***************************************************************************/
@Override
public List<QRecord> postQuery(QueryOrGetInputInterface queryInput, List<QRecord> records) throws QException
{
for(TableCustomizerInterface customizer : customizers)
{
records = customizer.postQuery(queryInput, records);
}
return records;
}
/***************************************************************************
* run preInsert method over all sub-customizers
***************************************************************************/
@Override
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
for(TableCustomizerInterface customizer : customizers)
{
records = customizer.preInsert(insertInput, records, isPreview);
}
return records;
}
/***************************************************************************
* run postInsert method over all sub-customizers
***************************************************************************/
@Override
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
{
for(TableCustomizerInterface customizer : customizers)
{
records = customizer.postInsert(insertInput, records);
}
return records;
}
/***************************************************************************
* run preUpdate method over all sub-customizers
***************************************************************************/
@Override
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{
for(TableCustomizerInterface customizer : customizers)
{
records = customizer.preUpdate(updateInput, records, isPreview, oldRecordList);
}
return records;
}
/***************************************************************************
* run postUpdate method over all sub-customizers
***************************************************************************/
@Override
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
for(TableCustomizerInterface customizer : customizers)
{
records = customizer.postUpdate(updateInput, records, oldRecordList);
}
return records;
}
/***************************************************************************
* run preDelete method over all sub-customizers
***************************************************************************/
@Override
public List<QRecord> preDelete(DeleteInput deleteInput, List<QRecord> records, boolean isPreview) throws QException
{
for(TableCustomizerInterface customizer : customizers)
{
records = customizer.preDelete(deleteInput, records, isPreview);
}
return records;
}
/***************************************************************************
* run postDelete method over all sub-customizers
***************************************************************************/
@Override
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
{
for(TableCustomizerInterface customizer : customizers)
{
records = customizer.postDelete(deleteInput, records);
}
return records;
}
}

View File

@ -1,88 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.customizers;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.utils.collections.TypeTolerantKeyMap;
/*******************************************************************************
** utility class to help table customizers working with the oldRecordList.
** Usage is just 2 lines:
** outside of loop-over-records:
** - OldRecordHelper oldRecordHelper = new OldRecordHelper(updateInput.getTableName(), oldRecordList);
** then inside the record loop:
** - Optional<QRecord> oldRecord = oldRecordHelper.getOldRecord(record);
*******************************************************************************/
public class OldRecordHelper
{
private String primaryKeyField;
private QFieldType primaryKeyType;
private Optional<List<QRecord>> oldRecordList;
private Map<Serializable, QRecord> oldRecordMap;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public OldRecordHelper(String tableName, Optional<List<QRecord>> oldRecordList)
{
this.primaryKeyField = QContext.getQInstance().getTable(tableName).getPrimaryKeyField();
this.primaryKeyType = QContext.getQInstance().getTable(tableName).getField(primaryKeyField).getType();
this.oldRecordList = oldRecordList;
}
/***************************************************************************
**
***************************************************************************/
public Optional<QRecord> getOldRecord(QRecord record)
{
if(oldRecordMap == null)
{
if(oldRecordList.isPresent())
{
oldRecordMap = new TypeTolerantKeyMap<>(primaryKeyType);
oldRecordList.get().forEach(r -> oldRecordMap.put(r.getValue(primaryKeyField), r));
}
else
{
oldRecordMap = Collections.emptyMap();
}
}
return (Optional.ofNullable(oldRecordMap.get(record.getValue(primaryKeyField))));
}
}

View File

@ -161,7 +161,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
public static String linkTableCreateWithDefaultValues(RenderWidgetInput input, String tableName, Map<String, Serializable> defaultValues) throws QException
{
String tablePath = QContext.getQInstance().getTablePath(tableName);
return (tablePath + "/create#defaultValues=" + URLEncoder.encode(JsonUtils.toJson(defaultValues), StandardCharsets.UTF_8));
return (tablePath + "/create#defaultValues=" + URLEncoder.encode(JsonUtils.toJson(defaultValues), Charset.defaultCharset()));
}
@ -229,7 +229,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
}
filter = QQueryFilterDeduper.dedupeFilter(filter);
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
}

View File

@ -25,14 +25,11 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
import java.io.Serializable;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gson.reflect.TypeToken;
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
@ -48,7 +45,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
@ -69,7 +65,6 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.core.utils.collections.MutableList;
import org.apache.commons.lang.BooleanUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -181,18 +176,6 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public Builder withOmitFieldNames(List<String> omitFieldNames)
{
ArrayList<String> arrayList = CollectionUtils.useOrWrap(omitFieldNames, new TypeToken<>() {});
widgetMetaData.withDefaultValue("omitFieldNames", arrayList);
return (this);
}
}
@ -212,25 +195,14 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
QTableMetaData leftTable = QContext.getQInstance().getTable(join.getLeftTable());
QTableMetaData rightTable = QContext.getQInstance().getTable(join.getRightTable());
Map<String, Serializable> widgetMetaDataDefaultValues = input.getWidgetMetaData().getDefaultValues();
List<String> omitFieldNames = (List<String>) widgetMetaDataDefaultValues.get("omitFieldNames");
if(omitFieldNames == null)
{
omitFieldNames = new ArrayList<>();
}
else
{
omitFieldNames = new MutableList<>(omitFieldNames);
}
Integer maxRows = null;
if(StringUtils.hasContent(input.getQueryParams().get("maxRows")))
{
maxRows = ValueUtils.getValueAsInteger(input.getQueryParams().get("maxRows"));
}
else if(widgetMetaDataDefaultValues.containsKey("maxRows"))
else if(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"))
{
maxRows = ValueUtils.getValueAsInteger(widgetMetaDataDefaultValues.get("maxRows"));
maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().get("maxRows"));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -262,19 +234,8 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
for(JoinOn joinOn : join.getJoinOns())
{
filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.EQUALS, List.of(primaryRecord.getValue(joinOn.getLeftField()))));
omitFieldNames.add(joinOn.getRightField());
}
Serializable orderBy = widgetMetaDataDefaultValues.get("orderBy");
if(orderBy instanceof List orderByList && !orderByList.isEmpty() && orderByList.get(0) instanceof QFilterOrderBy)
{
filter.setOrderBys(orderByList);
}
else
{
filter.setOrderBys(join.getOrderBys());
}
filter.setOrderBys(join.getOrderBys());
filter.setLimit(maxRows);
QueryInput queryInput = new QueryInput();
@ -301,10 +262,9 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
}
String tablePath = QContext.getQInstance().getTablePath(rightTable.getName());
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
ChildRecordListData widgetData = new ChildRecordListData(widgetLabel, queryOutput, rightTable, tablePath, viewAllLink, totalRows);
widgetData.setOmitFieldNames(omitFieldNames);
if(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("canAddChildRecord"))))
{
@ -324,10 +284,11 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
widgetData.setDefaultValuesForNewChildRecords(defaultValuesForNewChildRecords);
if(widgetMetaDataDefaultValues.containsKey("disabledFieldsForNewChildRecords"))
Map<String, Serializable> widgetValues = input.getWidgetMetaData().getDefaultValues();
if(widgetValues.containsKey("disabledFieldsForNewChildRecords"))
{
@SuppressWarnings("unchecked")
Set<String> disabledFieldsForNewChildRecords = (Set<String>) widgetMetaDataDefaultValues.get("disabledFieldsForNewChildRecords");
Set<String> disabledFieldsForNewChildRecords = (Set<String>) widgetValues.get("disabledFieldsForNewChildRecords");
widgetData.setDisabledFieldsForNewChildRecords(disabledFieldsForNewChildRecords);
}
else
@ -347,10 +308,10 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
}
}
if(widgetMetaDataDefaultValues.containsKey("defaultValuesForNewChildRecordsFromParentFields"))
if(widgetValues.containsKey("defaultValuesForNewChildRecordsFromParentFields"))
{
@SuppressWarnings("unchecked")
Map<String, String> defaultValuesForNewChildRecordsFromParentFields = (Map<String, String>) widgetMetaDataDefaultValues.get("defaultValuesForNewChildRecordsFromParentFields");
Map<String, String> defaultValuesForNewChildRecordsFromParentFields = (Map<String, String>) widgetValues.get("defaultValuesForNewChildRecordsFromParentFields");
widgetData.setDefaultValuesForNewChildRecordsFromParentFields(defaultValuesForNewChildRecordsFromParentFields);
}
}

View File

@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
@ -195,7 +194,7 @@ public class RecordListWidgetRenderer extends AbstractWidgetRenderer
}
String tablePath = QContext.getQInstance().getTablePath(tableName);
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
ChildRecordListData widgetData = new ChildRecordListData(input.getQueryParams().get("widgetLabel"), queryOutput, table, tablePath, viewAllLink, totalRows);

View File

@ -299,8 +299,6 @@ public class MetaDataAction
metaDataOutput.setHelpContents(Objects.requireNonNullElse(QContext.getQInstance().getHelpContent(), Collections.emptyMap()));
metaDataOutput.setSupplementalInstanceMetaData(QContext.getQInstance().getSupplementalMetaData());
try
{
customizer.postProcess(metaDataOutput);
@ -331,25 +329,23 @@ public class MetaDataAction
if(metaDataActionCustomizerReference != null)
{
actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataActionCustomizerReference);
LOG.debug("Using new meta-data actionCustomizer of type: " + actionCustomizer.getClass().getSimpleName());
}
if(actionCustomizer == null)
{
/////////////////////////////////////////////////////////////////////////////////////
// check if QInstance is still using the now-deprecated getMetaDataFilter approach //
/////////////////////////////////////////////////////////////////////////////////////
@SuppressWarnings("deprecation")
QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
if(metaDataFilterReference != null)
{
LOG.warn("QInstance.metaDataFilter is deprecated in favor of metaDataActionCustomizer.");
actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataFilterReference);
LOG.debug("Using new meta-data actionCustomizer (via metaDataFilter reference) of type: " + actionCustomizer.getClass().getSimpleName());
}
}
if(actionCustomizer == null)
{
actionCustomizer = new DefaultNoopMetaDataActionCustomizer();
LOG.debug("Using new default (allow-all) meta-data actionCustomizer");
}
return (actionCustomizer);

View File

@ -198,10 +198,10 @@ public class RunBackendStepAction
//////////////////////////////////////////////////
// look for record ids in the input data values //
//////////////////////////////////////////////////
String recordIds = runBackendStepInput.getValueString("recordIds");
String recordIds = (String) runBackendStepInput.getValue("recordIds");
if(recordIds == null)
{
recordIds = runBackendStepInput.getValueString("recordId");
recordIds = (String) runBackendStepInput.getValue("recordId");
}
///////////////////////////////////////////////////////////

View File

@ -246,7 +246,6 @@ public class DeleteAction
{
DMLAuditInput dmlAuditInput = new DMLAuditInput()
.withTableActionInput(deleteInput)
.withTransaction(deleteInput.getTransaction())
.withAuditContext(deleteInput.getAuditContext());
oldRecordList.ifPresent(l -> dmlAuditInput.setRecordList(l));
new DMLAuditAction().execute(dmlAuditInput);
@ -402,7 +401,6 @@ public class DeleteAction
if(CollectionUtils.nullSafeHasContents(associatedKeys))
{
DeleteInput nextLevelDeleteInput = new DeleteInput();
nextLevelDeleteInput.setFlags(deleteInput.getFlags());
nextLevelDeleteInput.setTransaction(deleteInput.getTransaction());
nextLevelDeleteInput.setTableName(association.getAssociatedTableName());
nextLevelDeleteInput.setPrimaryKeys(associatedKeys);

View File

@ -34,6 +34,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
@ -53,7 +54,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
@ -157,7 +157,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
//////////////////////////////////////////////////
// insert any associations in the input records //
//////////////////////////////////////////////////
manageAssociations(table, insertOutput.getRecords(), insertInput);
manageAssociations(table, insertOutput.getRecords(), insertInput.getTransaction());
//////////////////
// do the audit //
@ -170,26 +170,13 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
{
new DMLAuditAction().execute(new DMLAuditInput()
.withTableActionInput(insertInput)
.withTransaction(insertInput.getTransaction())
.withAuditContext(insertInput.getAuditContext())
.withRecordList(insertOutput.getRecords()));
}
////////////////////////////////////////////////////////////////
// finally, run the post-insert customizers, if there are any //
////////////////////////////////////////////////////////////////
runPostInsertCustomizers(insertInput, table, insertOutput);
return insertOutput;
}
/***************************************************************************
**
***************************************************************************/
private static void runPostInsertCustomizers(InsertInput insertInput, QTableMetaData table, InsertOutput insertOutput)
{
//////////////////////////////////////////////////////////////
// finally, run the post-insert customizer, if there is one //
//////////////////////////////////////////////////////////////
Optional<TableCustomizerInterface> postInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_INSERT_RECORD.getRole());
if(postInsertCustomizer.isPresent())
{
@ -206,25 +193,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
}
}
///////////////////////////////////////////////
// run all of the instance-level customizers //
///////////////////////////////////////////////
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.POST_INSERT_RECORD);
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
{
try
{
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
insertOutput.setRecords(tableCustomizer.postInsert(insertInput, insertOutput.getRecords()));
}
catch(Exception e)
{
for(QRecord record : insertOutput.getRecords())
{
record.addWarning(new QWarningMessage("An error occurred after the insert: " + e.getMessage()));
}
}
}
return insertOutput;
}
@ -339,19 +308,6 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
insertInput.setRecords(preInsertCustomizer.get().preInsert(insertInput, insertInput.getRecords(), isPreview));
}
}
///////////////////////////////////////////////
// run all of the instance-level customizers //
///////////////////////////////////////////////
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.PRE_INSERT_RECORD);
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
{
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
if(whenToRun.equals(tableCustomizer.whenToRunPreInsert(insertInput, isPreview)))
{
insertInput.setRecords(tableCustomizer.preInsert(insertInput, insertInput.getRecords(), isPreview));
}
}
}
@ -386,7 +342,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
/*******************************************************************************
**
*******************************************************************************/
private void manageAssociations(QTableMetaData table, List<QRecord> insertedRecords, InsertInput insertInput) throws QException
private void manageAssociations(QTableMetaData table, List<QRecord> insertedRecords, QBackendTransaction transaction) throws QException
{
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
{
@ -419,8 +375,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
if(CollectionUtils.nullSafeHasContents(nextLevelInserts))
{
InsertInput nextLevelInsertInput = new InsertInput();
nextLevelInsertInput.withFlags(insertInput.getFlags());
nextLevelInsertInput.setTransaction(insertInput.getTransaction());
nextLevelInsertInput.setTransaction(transaction);
nextLevelInsertInput.setTableName(association.getAssociatedTableName());
nextLevelInsertInput.setRecords(nextLevelInserts);
InsertOutput nextLevelInsertOutput = new InsertAction().execute(nextLevelInsertInput);

View File

@ -126,7 +126,6 @@ public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, Replace
InsertInput insertInput = new InsertInput();
insertInput.setTableName(table.getName());
insertInput.setRecords(insertList);
insertInput.withFlags(input.getFlags());
insertInput.setTransaction(transaction);
insertInput.setOmitDmlAudit(input.getOmitDmlAudit());
InsertOutput insertOutput = new InsertAction().execute(insertInput);
@ -136,7 +135,6 @@ public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, Replace
UpdateInput updateInput = new UpdateInput();
updateInput.setTableName(table.getName());
updateInput.setRecords(updateList);
updateInput.withFlags(input.getFlags());
updateInput.setTransaction(transaction);
updateInput.setOmitDmlAudit(input.getOmitDmlAudit());
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
@ -153,7 +151,6 @@ public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, Replace
DeleteInput deleteInput = new DeleteInput();
deleteInput.setTableName(table.getName());
deleteInput.setQueryFilter(deleteFilter);
deleteInput.withFlags(input.getFlags());
deleteInput.setTransaction(transaction);
deleteInput.setOmitDmlAudit(input.getOmitDmlAudit());
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);

View File

@ -57,7 +57,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
@ -190,7 +189,6 @@ public class UpdateAction
else
{
DMLAuditInput dmlAuditInput = new DMLAuditInput()
.withTransaction(updateInput.getTransaction())
.withTableActionInput(updateInput)
.withRecordList(updateOutput.getRecords())
.withAuditContext(updateInput.getAuditContext());
@ -201,18 +199,6 @@ public class UpdateAction
//////////////////////////////////////////////////////////////
// finally, run the post-update customizer, if there is one //
//////////////////////////////////////////////////////////////
runPostUpdateCustomizers(updateInput, table, updateOutput, oldRecordList);
return updateOutput;
}
/***************************************************************************
**
***************************************************************************/
private static void runPostUpdateCustomizers(UpdateInput updateInput, QTableMetaData table, UpdateOutput updateOutput, Optional<List<QRecord>> oldRecordList)
{
Optional<TableCustomizerInterface> postUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_UPDATE_RECORD.getRole());
if(postUpdateCustomizer.isPresent())
{
@ -229,49 +215,7 @@ public class UpdateAction
}
}
///////////////////////////////////////////////
// run all of the instance-level customizers //
///////////////////////////////////////////////
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.POST_UPDATE_RECORD);
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
{
try
{
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
updateOutput.setRecords(tableCustomizer.postUpdate(updateInput, updateOutput.getRecords(), oldRecordList));
}
catch(Exception e)
{
for(QRecord record : updateOutput.getRecords())
{
record.addWarning(new QWarningMessage("An error occurred after the update: " + e.getMessage()));
}
}
}
}
/***************************************************************************
**
***************************************************************************/
private static void runPreUpdateCustomizers(UpdateInput updateInput, QTableMetaData table, Optional<List<QRecord>> oldRecordList, boolean isPreview) throws QException
{
Optional<TableCustomizerInterface> preUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
if(preUpdateCustomizer.isPresent())
{
updateInput.setRecords(preUpdateCustomizer.get().preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList));
}
///////////////////////////////////////////////
// run all of the instance-level customizers //
///////////////////////////////////////////////
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.PRE_UPDATE_RECORD);
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
{
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
updateInput.setRecords(tableCustomizer.preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList));
}
return updateOutput;
}
@ -334,7 +278,11 @@ public class UpdateAction
///////////////////////////////////////////////////////////////////////////
// after all validations, run the pre-update customizer, if there is one //
///////////////////////////////////////////////////////////////////////////
runPreUpdateCustomizers(updateInput, table, oldRecordList, isPreview);
Optional<TableCustomizerInterface> preUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
if(preUpdateCustomizer.isPresent())
{
updateInput.setRecords(preUpdateCustomizer.get().preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList));
}
}
@ -457,7 +405,7 @@ public class UpdateAction
QFieldType fieldType = table.getField(lock.getFieldName()).getType();
Serializable lockValue = ValueUtils.getValueAsFieldType(fieldType, oldRecord.getValue(lock.getFieldName()));
List<QErrorMessage> errors = ValidateRecordSecurityLockHelper.validateRecordSecurityValue(table, lock, lockValue, fieldType, ValidateRecordSecurityLockHelper.Action.UPDATE, Collections.emptyMap(), QContext.getQSession());
List<QErrorMessage> errors = ValidateRecordSecurityLockHelper.validateRecordSecurityValue(table, lock, lockValue, fieldType, ValidateRecordSecurityLockHelper.Action.UPDATE, Collections.emptyMap());
if(CollectionUtils.nullSafeHasContents(errors))
{
errors.forEach(e -> record.addError(e));
@ -606,7 +554,6 @@ public class UpdateAction
{
LOG.debug("Deleting associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", queryOutput.getRecords().size()));
DeleteInput deleteInput = new DeleteInput();
deleteInput.setFlags(updateInput.getFlags());
deleteInput.setTransaction(updateInput.getTransaction());
deleteInput.setTableName(association.getAssociatedTableName());
deleteInput.setPrimaryKeys(queryOutput.getRecords().stream().map(r -> r.getValue(associatedTable.getPrimaryKeyField())).collect(Collectors.toList()));
@ -619,7 +566,6 @@ public class UpdateAction
LOG.debug("Updating associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", nextLevelUpdates.size()));
UpdateInput nextLevelUpdateInput = new UpdateInput();
nextLevelUpdateInput.setTransaction(updateInput.getTransaction());
nextLevelUpdateInput.setFlags(updateInput.getFlags());
nextLevelUpdateInput.setTableName(association.getAssociatedTableName());
nextLevelUpdateInput.setRecords(nextLevelUpdates);
UpdateOutput nextLevelUpdateOutput = new UpdateAction().execute(nextLevelUpdateInput);
@ -630,7 +576,6 @@ public class UpdateAction
LOG.debug("Inserting associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", nextLevelUpdates.size()));
InsertInput nextLevelInsertInput = new InsertInput();
nextLevelInsertInput.setTransaction(updateInput.getTransaction());
nextLevelInsertInput.setFlags(updateInput.getFlags());
nextLevelInsertInput.setTableName(association.getAssociatedTableName());
nextLevelInsertInput.setRecords(nextLevelInserts);
InsertOutput nextLevelInsertOutput = new InsertAction().execute(nextLevelInsertInput);

View File

@ -41,7 +41,6 @@ import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
@ -570,7 +569,7 @@ public class QueryActionCacheHelper
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
sourceQueryInput.setFilter(filter);
((QueryOrGetInputInterface) sourceQueryInput).setCommonParamsFrom(cacheQueryInput);
sourceQueryInput.setCommonParamsFrom(cacheQueryInput);
for(List<Serializable> uniqueKeyValue : uniqueKeyValues)
{

View File

@ -50,7 +50,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLockFilters;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.model.statusmessages.PermissionDeniedMessage;
import com.kingsrook.qqq.backend.core.model.statusmessages.QErrorMessage;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -103,7 +102,7 @@ public class ValidateRecordSecurityLockHelper
// actually check lock values //
////////////////////////////////
Map<Serializable, RecordWithErrors> errorRecords = new HashMap<>();
evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>(), madeUpPrimaryKeys, transaction, QContext.getQSession());
evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>(), madeUpPrimaryKeys, transaction);
/////////////////////////////////
// propagate errors to records //
@ -125,29 +124,6 @@ public class ValidateRecordSecurityLockHelper
/***************************************************************************
** return boolean if given session can read given record
***************************************************************************/
public static boolean allowedToReadRecord(QTableMetaData table, QRecord record, QSession qSession, QBackendTransaction transaction) throws QException
{
MultiRecordSecurityLock locksToCheck = getRecordSecurityLocks(table, Action.SELECT);
if(locksToCheck == null || CollectionUtils.nullSafeIsEmpty(locksToCheck.getLocks()))
{
return (true);
}
Map<Serializable, RecordWithErrors> errorRecords = new HashMap<>();
evaluateRecordLocks(table, List.of(record), Action.SELECT, locksToCheck, errorRecords, new ArrayList<>(), Collections.emptyMap(), transaction, qSession);
if(errorRecords.containsKey(record.getValue(table.getPrimaryKeyField())))
{
return (false);
}
return (true);
}
/*******************************************************************************
** For a list of `records` from a `table`, and a given `action`, evaluate a
** `recordSecurityLock` (which may be a multi-lock) - populating the input map
@ -166,7 +142,7 @@ public class ValidateRecordSecurityLockHelper
** BUT - WRITE locks - in their case, we read the record no matter what, and in
** here we need to verify we have a key that allows us to WRITE the record.
*******************************************************************************/
private static void evaluateRecordLocks(QTableMetaData table, List<QRecord> records, Action action, RecordSecurityLock recordSecurityLock, Map<Serializable, RecordWithErrors> errorRecords, List<Integer> treePosition, Map<Serializable, QRecord> madeUpPrimaryKeys, QBackendTransaction transaction, QSession qSession) throws QException
private static void evaluateRecordLocks(QTableMetaData table, List<QRecord> records, Action action, RecordSecurityLock recordSecurityLock, Map<Serializable, RecordWithErrors> errorRecords, List<Integer> treePosition, Map<Serializable, QRecord> madeUpPrimaryKeys, QBackendTransaction transaction) throws QException
{
if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock)
{
@ -177,7 +153,7 @@ public class ValidateRecordSecurityLockHelper
for(RecordSecurityLock childLock : CollectionUtils.nonNullList(multiRecordSecurityLock.getLocks()))
{
treePosition.add(i);
evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition, madeUpPrimaryKeys, transaction, qSession);
evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition, madeUpPrimaryKeys, transaction);
treePosition.remove(treePosition.size() - 1);
i++;
}
@ -189,7 +165,7 @@ public class ValidateRecordSecurityLockHelper
// if this lock has an all-access key, and the user has that key, then there can't be any errors here, so return early //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && qSession.hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && QContext.getQSession().hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
{
return;
}
@ -217,7 +193,7 @@ public class ValidateRecordSecurityLockHelper
}
Serializable recordSecurityValue = record.getValue(field.getName());
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys, qSession);
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys);
if(CollectionUtils.nullSafeHasContents(recordErrors))
{
errorRecords.computeIfAbsent(record.getValue(primaryKeyField), (k) -> new RecordWithErrors(record)).addAll(recordErrors, treePosition);
@ -363,7 +339,7 @@ public class ValidateRecordSecurityLockHelper
for(QRecord inputRecord : inputRecords)
{
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys, qSession);
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys);
if(CollectionUtils.nullSafeHasContents(recordErrors))
{
errorRecords.computeIfAbsent(inputRecord.getValue(primaryKeyField), (k) -> new RecordWithErrors(inputRecord)).addAll(recordErrors, treePosition);
@ -470,7 +446,7 @@ public class ValidateRecordSecurityLockHelper
/*******************************************************************************
**
*******************************************************************************/
public static List<QErrorMessage> validateRecordSecurityValue(QTableMetaData table, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action, Map<Serializable, QRecord> madeUpPrimaryKeys, QSession qSession)
public static List<QErrorMessage> validateRecordSecurityValue(QTableMetaData table, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action, Map<Serializable, QRecord> madeUpPrimaryKeys)
{
if(recordSecurityValue == null || (madeUpPrimaryKeys != null && madeUpPrimaryKeys.containsKey(recordSecurityValue)))
{
@ -485,7 +461,7 @@ public class ValidateRecordSecurityLockHelper
}
else
{
if(!qSession.hasSecurityKeyValue(recordSecurityLock.getSecurityKeyType(), recordSecurityValue, fieldType))
if(!QContext.getQSession().hasSecurityKeyValue(recordSecurityLock.getSecurityKeyType(), recordSecurityValue, fieldType))
{
if(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()))
{

View File

@ -35,7 +35,6 @@ import com.openhtmltopdf.pdfboxout.PdfBoxFontResolver;
import com.openhtmltopdf.pdfboxout.PdfBoxRenderer;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import org.jsoup.Jsoup;
import org.jsoup.helper.W3CDom;
import org.jsoup.nodes.Document;
@ -68,7 +67,6 @@ public class ConvertHtmlToPdfAction extends AbstractQActionFunction<ConvertHtmlT
//////////////////////////////////////////////////////////////////
Document document = Jsoup.parse(input.getHtml());
document.outputSettings().syntax(Document.OutputSettings.Syntax.xml);
org.w3c.dom.Document w3cDoc = new W3CDom().fromJsoup(document);
//////////////////////////////
// convert the XHTML to PDF //
@ -76,7 +74,7 @@ public class ConvertHtmlToPdfAction extends AbstractQActionFunction<ConvertHtmlT
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.toStream(input.getOutputStream());
builder.useFastMode();
builder.withW3cDocument(w3cDoc, input.getBasePath() == null ? "./" : input.getBasePath().toUri().toString());
builder.withHtmlContent(document.html(), input.getBasePath() == null ? "./" : input.getBasePath().toUri().toString());
try(PdfBoxRenderer pdfBoxRenderer = builder.buildPdfRenderer())
{

View File

@ -1,91 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.actions.values;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
/*******************************************************************************
** Basic implementation of a possible value provider, for where there's a limited
** set of possible source objects - so you just have to define how to make one
** PV from a source object, how to list all of the source objects, and how to
** look up a PV from an id.
*******************************************************************************/
public abstract class BasicCustomPossibleValueProvider<S, ID extends Serializable> implements QCustomPossibleValueProvider<ID>
{
/***************************************************************************
**
***************************************************************************/
protected abstract QPossibleValue<ID> makePossibleValue(S sourceObject);
/***************************************************************************
**
***************************************************************************/
protected abstract S getSourceObject(Serializable id) throws QException;
/***************************************************************************
**
***************************************************************************/
protected abstract List<S> getAllSourceObjects() throws QException;
/***************************************************************************
**
***************************************************************************/
@Override
public QPossibleValue<ID> getPossibleValue(Serializable idValue) throws QException
{
S sourceObject = getSourceObject(idValue);
if(sourceObject == null)
{
return (null);
}
return makePossibleValue(sourceObject);
}
/***************************************************************************
**
***************************************************************************/
@Override
public List<QPossibleValue<ID>> search(SearchPossibleValueSourceInput input) throws QException
{
List<QPossibleValue<ID>> allPossibleValues = new ArrayList<>();
List<S> allSourceObjects = getAllSourceObjects();
for(S sourceObject : allSourceObjects)
{
allPossibleValues.add(makePossibleValue(sourceObject));
}
return completeCustomPVSSearch(input, allPossibleValues);
}
}

View File

@ -45,7 +45,7 @@ public interface QCustomPossibleValueProvider<T extends Serializable>
/*******************************************************************************
**
*******************************************************************************/
QPossibleValue<T> getPossibleValue(Serializable idValue) throws QException;
QPossibleValue<T> getPossibleValue(Serializable idValue);
/*******************************************************************************
**

View File

@ -310,19 +310,6 @@ public class QValueFormatter
/*******************************************************************************
** For a list of records, set their recordLabels and display values - including
** record label (e.g., from the table meta data).
*******************************************************************************/
public static void setDisplayValuesInRecordsIncludingPossibleValueTranslations(QTableMetaData table, List<QRecord> records)
{
QPossibleValueTranslator possibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), QContext.getQSession());
possibleValueTranslator.translatePossibleValuesInRecords(table, records);
setDisplayValuesInRecords(table, records);
}
/*******************************************************************************
** For a list of records, set their recordLabels and display values - including
** record label (e.g., from the table meta data).

View File

@ -29,7 +29,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Version of AbstractQQQApplication that assumes all meta-data is produced
** by MetaDataProducers in (or under) a single package.
** by MetaDataProducers in a single package.
*******************************************************************************/
public abstract class AbstractMetaDataProducerBasedQQQApplication extends AbstractQQQApplication
{

View File

@ -1,61 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.loaders.MetaDataLoaderHelper;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Version of AbstractQQQApplication that assumes all meta-data is defined in
** config files (yaml, json, etc) under a given directory path.
*******************************************************************************/
public class ConfigFilesBasedQQQApplication extends AbstractQQQApplication
{
private final String path;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ConfigFilesBasedQQQApplication(String path)
{
this.path = path;
}
/***************************************************************************
**
***************************************************************************/
@Override
public QInstance defineQInstance() throws QException
{
QInstance qInstance = new QInstance();
MetaDataLoaderHelper.processAllMetaDataFilesInDirectory(qInstance, path);
return (qInstance);
}
}

View File

@ -1,67 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances;
/*******************************************************************************
** Version of AbstractQQQApplication that assumes all meta-data is produced
** by MetaDataProducers in (or under) a single package (where you can pass that
** package into the constructor, vs. the abstract base class, where you extend
** it and override the getMetaDataPackageName method.
*******************************************************************************/
public class MetaDataProducerBasedQQQApplication extends AbstractMetaDataProducerBasedQQQApplication
{
private final String metaDataPackageName;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public MetaDataProducerBasedQQQApplication(String metaDataPackageName)
{
this.metaDataPackageName = metaDataPackageName;
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public MetaDataProducerBasedQQQApplication(Class<?> aClassInMetaDataPackage)
{
this(aClassInMetaDataPackage.getPackageName());
}
/***************************************************************************
**
***************************************************************************/
@Override
public String getMetaDataPackageName()
{
return (this.metaDataPackageName);
}
}

View File

@ -1,46 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
/*******************************************************************************
* interface that can be added to a QSupplementalInstanceMetaData, to receive
* QHelpContent records during instance boot or upon updates in the help content
* table.
*******************************************************************************/
public interface QHelpContentPlugin
{
/***************************************************************************
* accept a single helpContent record, and apply its data to some data in the
* qInstance
*
* @param qInstance the active qInstance, that the content should be applied to
* @param helpContent entity with values from HelpContent table
* @param nameValuePairs parsed string -> string map from the help content key.
***************************************************************************/
void acceptHelpContent(QInstance qInstance, QHelpContent helpContent, Map<String, String> nameValuePairs);
}

View File

@ -31,9 +31,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -45,12 +43,10 @@ import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
import com.kingsrook.qqq.backend.core.actions.permissions.BulkTableActionProcessPermissionChecker;
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
import com.kingsrook.qqq.backend.core.instances.enrichment.plugins.QInstanceEnricherPluginInterface;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
@ -59,7 +55,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueB
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QSupplementalFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
@ -215,37 +210,6 @@ public class QInstanceEnricher
***************************************************************************/
private void enrichInstance()
{
////////////////////////////////////////////////////////////////////////////////////
// enriching some objects may cause additional ones to be added to the qInstance! //
// this caused concurrent modification exceptions, when we just iterated. //
// we could make a copy of the map and just process that, but then we wouldn't //
// enrich any new objects that do get added, so, use this technique instead. //
////////////////////////////////////////////////////////////////////////////////////
Set<QSupplementalInstanceMetaData> toEnrich = new LinkedHashSet<>(qInstance.getSupplementalMetaData().values());
Set<QSupplementalInstanceMetaData> enriched = new HashSet<>();
int count = 0;
while(!toEnrich.isEmpty())
{
Iterator<QSupplementalInstanceMetaData> iterator = toEnrich.iterator();
QSupplementalInstanceMetaData supplementalInstanceMetaData = iterator.next();
iterator.remove();
supplementalInstanceMetaData.enrich(qInstance);
enriched.add(supplementalInstanceMetaData);
for(QSupplementalInstanceMetaData possiblyNew : qInstance.getSupplementalMetaData().values())
{
if(!toEnrich.contains(possiblyNew) && !enriched.contains(possiblyNew))
{
if(count++ > 100)
{
throw (new QRuntimeException("Too many new QSupplementalInstanceMetaData objects were added while enriching others. This probably indicates a bug in enrichment code. Throwing to prevent infinite loop."));
}
toEnrich.add(possiblyNew);
}
}
}
runPlugins(QInstance.class, qInstance, qInstance);
}
@ -366,21 +330,7 @@ public class QInstanceEnricher
if(table.getFields() != null)
{
for(Map.Entry<String, QFieldMetaData> entry : table.getFields().entrySet())
{
String name = entry.getKey();
QFieldMetaData field = entry.getValue();
////////////////////////////////////////////////////////////////////////////
// in case the field wasn't given a name, use its key from the fields map //
////////////////////////////////////////////////////////////////////////////
if(!StringUtils.hasContent(field.getName()))
{
field.setName(name);
}
enrichField(field);
}
table.getFields().values().forEach(this::enrichField);
for(QSupplementalTableMetaData supplementalTableMetaData : CollectionUtils.nonNullMap(table.getSupplementalMetaData()).values())
{
@ -618,14 +568,6 @@ public class QInstanceEnricher
}
}
////////////////////////////////////////////////////
// enrich any supplemental meta data on the field //
////////////////////////////////////////////////////
for(QSupplementalFieldMetaData supplementalFieldMetaData : CollectionUtils.nonNullMap(field.getSupplementalMetaData()).values())
{
supplementalFieldMetaData.enrich(qInstance, field);
}
runPlugins(QFieldMetaData.class, field, qInstance);
}
@ -1028,16 +970,16 @@ public class QInstanceEnricher
.withCode(new QCodeReference(BulkInsertReceiveValueMappingStep.class));
int i = 0;
process.withStep(i++, prepareFileUploadStep);
process.withStep(i++, uploadScreen);
process.addStep(i++, prepareFileUploadStep);
process.addStep(i++, uploadScreen);
process.withStep(i++, prepareFileMappingStep);
process.withStep(i++, fileMappingScreen);
process.withStep(i++, receiveFileMappingStep);
process.addStep(i++, prepareFileMappingStep);
process.addStep(i++, fileMappingScreen);
process.addStep(i++, receiveFileMappingStep);
process.withStep(i++, prepareValueMappingStep);
process.withStep(i++, valueMappingScreen);
process.withStep(i++, receiveValueMappingStep);
process.addStep(i++, prepareValueMappingStep);
process.addStep(i++, valueMappingScreen);
process.addStep(i++, receiveValueMappingStep);
process.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW).setRecordListFields(editableFields);
@ -1097,7 +1039,7 @@ public class QInstanceEnricher
Fields whose switches are off will not be updated."""))
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.BULK_EDIT_FORM));
process.withStep(0, editScreen);
process.addStep(0, editScreen);
process.getFrontendStep("review").setRecordListFields(editableFields);
qInstance.addProcess(process);
}
@ -1448,10 +1390,10 @@ public class QInstanceEnricher
if(possibleValueSource.getIdType() == null)
{
QTableMetaData table = qInstance.getTable(possibleValueSource.getTableName());
if(table != null && table.getFields() != null)
if(table != null)
{
String primaryKeyField = table.getPrimaryKeyField();
QFieldMetaData primaryKeyFieldMetaData = CollectionUtils.nonNullMap(table.getFields()).get(primaryKeyField);
QFieldMetaData primaryKeyFieldMetaData = table.getFields().get(primaryKeyField);
if(primaryKeyFieldMetaData != null)
{
possibleValueSource.setIdType(primaryKeyFieldMetaData.getType());
@ -1485,7 +1427,7 @@ public class QInstanceEnricher
{
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
Method getPossibleValueMethod = customPossibleValueProvider.getClass().getMethod("getPossibleValue", Serializable.class);
Method getPossibleValueMethod = customPossibleValueProvider.getClass().getDeclaredMethod("getPossibleValue", Serializable.class);
Type returnType = getPossibleValueMethod.getGenericReturnType();
Type idType = ((ParameterizedType) returnType).getActualTypeArguments()[0];
@ -1521,18 +1463,7 @@ public class QInstanceEnricher
if(enrichMethod.isPresent())
{
Class<?> parameterType = enrichMethod.get().getParameterTypes()[0];
Set<String> existingPluginIdentifiers = enricherPlugins.getOrDefault(parameterType, Collections.emptyList())
.stream().map(p -> p.getPluginIdentifier())
.collect(Collectors.toSet());
if(existingPluginIdentifiers.contains(plugin.getPluginIdentifier()))
{
LOG.debug("Enricher plugin is already registered - not re-adding it", logPair("pluginIdentifer", plugin.getPluginIdentifier()));
}
else
{
enricherPlugins.add(parameterType, plugin);
}
enricherPlugins.add(parameterType, plugin);
}
else
{
@ -1552,17 +1483,6 @@ public class QInstanceEnricher
/*******************************************************************************
** Getter for enricherPlugins
**
*******************************************************************************/
public static ListingHash<Class<?>, QInstanceEnricherPluginInterface<?>> getEnricherPlugins()
{
return enricherPlugins;
}
/***************************************************************************
** scan the classpath for classes in the specified package name which
** implement the QInstanceEnricherPluginInterface - any found get added

View File

@ -36,7 +36,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.helpcontent.HelpContent;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.help.HelpFormat;
@ -164,23 +163,6 @@ public class QInstanceHelpContentManager
{
processHelpContentForInstance(qInstance, key, slotName, roles, helpContent);
}
else
{
for(QSupplementalInstanceMetaData supplementalInstanceMetaData : qInstance.getSupplementalMetaData().values())
{
if(supplementalInstanceMetaData instanceof QHelpContentPlugin helpContentPlugin)
{
try
{
helpContentPlugin.acceptHelpContent(qInstance, helpContent, nameValuePairs);
}
catch(Exception e)
{
LOG.warn("Error processing a helpContent record in a helpContentPlugin", e, logPair("pluginName", supplementalInstanceMetaData.getName()), logPair("id", record.getValue("id")));
}
}
}
}
}
catch(Exception e)
{

View File

@ -41,8 +41,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandlerInterface;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
@ -65,7 +64,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaDa
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceLambda;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.ParentWidgetMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
@ -73,7 +71,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QSupplementalFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
@ -241,10 +238,8 @@ public class QInstanceValidator
/***************************************************************************
* this method still supports the deprecated MetaDataFilter (plus its
* replacement, MetaDataActionCustomizer
**
***************************************************************************/
@SuppressWarnings("deprecation")
private void validateInstanceAttributes(QInstance qInstance)
{
if(qInstance.getMetaDataFilter() != null)
@ -256,17 +251,6 @@ public class QInstanceValidator
{
validateSimpleCodeReference("Instance metaDataActionCustomizer ", qInstance.getMetaDataActionCustomizer(), MetaDataActionCustomizerInterface.class);
}
if(qInstance.getTableCustomizers() != null)
{
for(Map.Entry<String, List<QCodeReference>> entry : qInstance.getTableCustomizers().entrySet())
{
for(QCodeReference codeReference : CollectionUtils.nonNullList(entry.getValue()))
{
validateSimpleCodeReference("Instance tableCustomizer of type " + entry.getKey() + ": ", codeReference, TableCustomizerInterface.class);
}
}
}
}
@ -298,18 +282,7 @@ public class QInstanceValidator
if(validateMethod.isPresent())
{
Class<?> parameterType = validateMethod.get().getParameterTypes()[0];
Set<String> existingPluginIdentifiers = validatorPlugins.getOrDefault(parameterType, Collections.emptyList())
.stream().map(p -> p.getPluginIdentifier())
.collect(Collectors.toSet());
if(existingPluginIdentifiers.contains(plugin.getPluginIdentifier()))
{
LOG.debug("Validator plugin is already registered - not re-adding it", logPair("pluginIdentifer", plugin.getPluginIdentifier()));
}
else
{
validatorPlugins.add(parameterType, plugin);
}
validatorPlugins.add(parameterType, plugin);
}
else
{
@ -329,17 +302,6 @@ public class QInstanceValidator
/*******************************************************************************
** Getter for validatorPlugins
**
*******************************************************************************/
public static ListingHash<Class<?>, QInstanceValidatorPluginInterface<?>> getValidatorPlugins()
{
return validatorPlugins;
}
/*******************************************************************************
**
*******************************************************************************/
@ -689,8 +651,6 @@ public class QInstanceValidator
validateSimpleCodeReference("Instance Authentication meta data customizer ", authentication.getCustomizer(), QAuthenticationModuleCustomizerInterface.class);
}
authentication.validate(qInstance, this);
runPlugins(QAuthenticationMetaData.class, authentication, qInstance);
}
}
@ -1185,21 +1145,6 @@ public class QInstanceValidator
}
}
}
validateFieldSupplementalMetaData(field, qInstance);
}
/***************************************************************************
**
***************************************************************************/
public void validateFieldSupplementalMetaData(QFieldMetaData field, QInstance qInstance)
{
for(QSupplementalFieldMetaData supplementalFieldMetaData : CollectionUtils.nonNullMap(field.getSupplementalMetaData()).values())
{
supplementalFieldMetaData.validate(qInstance, field, this);
}
}
@ -1392,7 +1337,7 @@ public class QInstanceValidator
numberSet++;
if(preAssertionsForCodeReference(action.getCodeReference(), actionPrefix))
{
validateSimpleCodeReference(actionPrefix + "code reference: ", action.getCodeReference(), RecordAutomationHandlerInterface.class);
validateSimpleCodeReference(actionPrefix + "code reference: ", action.getCodeReference(), RecordAutomationHandler.class);
}
}
@ -1461,7 +1406,7 @@ public class QInstanceValidator
//////////////////////////////////////////////////
// make sure the customizer can be instantiated //
//////////////////////////////////////////////////
Object customizerInstance = getInstanceOfCodeReference(prefix, customizerClass, codeReference);
Object customizerInstance = getInstanceOfCodeReference(prefix, customizerClass);
TableCustomizers tableCustomizer = TableCustomizers.forRole(roleName);
if(tableCustomizer == null)
@ -1522,13 +1467,8 @@ public class QInstanceValidator
/*******************************************************************************
**
*******************************************************************************/
private Object getInstanceOfCodeReference(String prefix, Class<?> clazz, QCodeReference codeReference)
private Object getInstanceOfCodeReference(String prefix, Class<?> clazz)
{
if(codeReference instanceof QCodeReferenceLambda<?> lambdaCodeReference)
{
return (lambdaCodeReference.getLambda());
}
Object instance = null;
try
{
@ -1707,26 +1647,21 @@ public class QInstanceValidator
Set<String> usedStepNames = new HashSet<>();
if(assertCondition(CollectionUtils.nullSafeHasContents(process.getStepList()), "At least 1 step must be defined in process " + processName + "."))
{
int index = -1;
int index = 0;
for(QStepMetaData step : process.getStepList())
{
index++;
if(assertCondition(StringUtils.hasContent(step.getName()), "Missing name for a step at index " + index + " in process " + processName))
{
assertCondition(!usedStepNames.contains(step.getName()), "Duplicate step name [" + step.getName() + "] in process " + processName);
usedStepNames.add(step.getName());
}
index++;
////////////////////////////////////////////
// validate instantiation of step classes //
////////////////////////////////////////////
if(step instanceof QBackendStepMetaData backendStepMetaData)
{
if(assertCondition(backendStepMetaData.getCode() != null, "Missing code for a backend step at index " + index + " in process " + processName))
{
validateSimpleCodeReference("Process " + processName + ", backend step at index " + index + ", code reference: ", backendStepMetaData.getCode(), BackendStep.class);
}
if(backendStepMetaData.getInputMetaData() != null && CollectionUtils.nullSafeHasContents(backendStepMetaData.getInputMetaData().getFieldList()))
{
for(QFieldMetaData fieldMetaData : backendStepMetaData.getInputMetaData().getFieldList())
@ -1753,8 +1688,6 @@ public class QInstanceValidator
validateSimpleCodeReference("Process " + processName + " code reference:", codeReference, expectedClass);
}
validateFieldSupplementalMetaData(fieldMetaData, qInstance);
}
}
}
@ -2292,7 +2225,8 @@ public class QInstanceValidator
/*******************************************************************************
**
*******************************************************************************/
public void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class<?>... anyOfExpectedClasses)
@SafeVarargs
private void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class<?>... anyOfExpectedClasses)
{
if(!preAssertionsForCodeReference(codeReference, prefix))
{
@ -2313,7 +2247,7 @@ public class QInstanceValidator
//////////////////////////////////////////////////
// make sure the customizer can be instantiated //
//////////////////////////////////////////////////
Object classInstance = getInstanceOfCodeReference(prefix, clazz, codeReference);
Object classInstance = getInstanceOfCodeReference(prefix, clazz);
////////////////////////////////////////////////////////////////////////
// make sure the customizer instance can be cast to the expected type //
@ -2336,11 +2270,6 @@ public class QInstanceValidator
Class<?> clazz = null;
try
{
if(codeReference instanceof QCodeReferenceLambda<?> lambdaCodeReference)
{
return (lambdaCodeReference.getLambda().getClass());
}
clazz = Class.forName(codeReference.getName());
}
catch(ClassNotFoundException e)

View File

@ -72,18 +72,6 @@ public class SecretsManagerUtils
** and write them to a .env file (backing up any pre-existing .env files first).
*******************************************************************************/
public static void writeEnvFromSecretsWithNamePrefix(String prefix) throws IOException
{
writeEnvFromSecretsWithNamePrefix(prefix, true);
}
/*******************************************************************************
** IF secret manager ENV vars are set,
** THEN lookup all secrets starting with the given prefix,
** and write them to a .env file (backing up any pre-existing .env files first).
*******************************************************************************/
public static void writeEnvFromSecretsWithNamePrefix(String prefix, boolean quoteValues) throws IOException
{
Optional<AWSSecretsManager> optionalSecretsManagerClient = getSecretsManagerClient();
if(optionalSecretsManagerClient.isPresent())
@ -103,9 +91,7 @@ public class SecretsManagerUtils
Optional<String> secretValue = getSecret(prefix, nameWithoutPrefix);
if(secretValue.isPresent())
{
String envLine = quoteValues
? nameWithoutPrefix + "=\"" + secretValue.get() + "\""
: nameWithoutPrefix + "=" + secretValue.get();
String envLine = nameWithoutPrefix + "=" + secretValue.get();
fullEnv.append(envLine).append('\n');
}
}

View File

@ -1,37 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances.assessment;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** marker for an object which can be processed by the QInstanceAssessor.
*******************************************************************************/
public interface Assessable
{
/***************************************************************************
**
***************************************************************************/
void assess(QInstanceAssessor qInstanceAssessor, QInstance qInstance);
}

View File

@ -1,219 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances.assessment;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** POC of a class that is meant to review meta-data for accuracy vs. real backends.
*******************************************************************************/
public class QInstanceAssessor
{
private static final QLogger LOG = QLogger.getLogger(QInstanceAssessor.class);
private final QInstance qInstance;
private List<String> errors = new ArrayList<>();
private List<String> warnings = new ArrayList<>();
private List<String> suggestions = new ArrayList<>();
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public QInstanceAssessor(QInstance qInstance)
{
this.qInstance = qInstance;
}
/*******************************************************************************
**
*******************************************************************************/
public void assess()
{
for(QBackendMetaData backend : qInstance.getBackends().values())
{
if(backend instanceof Assessable assessable)
{
assessable.assess(this, qInstance);
}
}
}
/*******************************************************************************
**
*******************************************************************************/
@SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
public String getSummary()
{
StringBuilder rs = new StringBuilder();
///////////////////////////
// print header & errors //
///////////////////////////
if(CollectionUtils.nullSafeIsEmpty(errors))
{
rs.append("Assessment passed with no errors! \uD83D\uDE0E\n");
}
else
{
rs.append("Assessment found the following ").append(StringUtils.plural(errors, "error", "errors")).append(": \uD83D\uDE32\n");
for(String error : errors)
{
rs.append(" - ").append(error).append("\n");
}
}
/////////////////////////////////////
// print warnings if there are any //
/////////////////////////////////////
if(CollectionUtils.nullSafeHasContents(warnings))
{
rs.append("\nAssessment found the following ").append(StringUtils.plural(warnings, "warning", "warnings")).append(": \uD83E\uDD28\n");
for(String warning : warnings)
{
rs.append(" - ").append(warning).append("\n");
}
}
//////////////////////////////////////////
// print suggestions, if there were any //
//////////////////////////////////////////
if(CollectionUtils.nullSafeHasContents(suggestions))
{
rs.append("\nThe following ").append(StringUtils.plural(suggestions, "fix is", "fixes are")).append(" suggested: \uD83E\uDD13\n");
for(String suggestion : suggestions)
{
rs.append("\n").append(suggestion).append("\n\n");
}
}
return (rs.toString());
}
/*******************************************************************************
** Getter for qInstance
**
*******************************************************************************/
public QInstance getInstance()
{
return qInstance;
}
/*******************************************************************************
** Getter for errors
**
*******************************************************************************/
public List<String> getErrors()
{
return errors;
}
/*******************************************************************************
** Getter for warnings
**
*******************************************************************************/
public List<String> getWarnings()
{
return warnings;
}
/*******************************************************************************
**
*******************************************************************************/
public void addError(String errorMessage)
{
errors.add(errorMessage);
}
/*******************************************************************************
**
*******************************************************************************/
public void addWarning(String warningMessage)
{
warnings.add(warningMessage);
}
/*******************************************************************************
**
*******************************************************************************/
public void addError(String errorMessage, Exception e)
{
addError(errorMessage + " : " + e.getMessage());
}
/*******************************************************************************
**
*******************************************************************************/
public void addSuggestion(String message)
{
suggestions.add(message);
}
/*******************************************************************************
**
*******************************************************************************/
public int getExitCode()
{
if(CollectionUtils.nullSafeHasContents(errors))
{
return (1);
}
else
{
return (0);
}
}
}

View File

@ -37,13 +37,4 @@ public interface QInstanceEnricherPluginInterface<T>
*******************************************************************************/
void enrich(T object, QInstance qInstance);
/***************************************************************************
**
***************************************************************************/
default String getPluginIdentifier()
{
return getClass().getName();
}
}

View File

@ -1,510 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.instances.loaders;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.core.type.TypeReference;
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.core.utils.YamlUtils;
import org.apache.commons.io.IOUtils;
import static com.kingsrook.qqq.backend.core.utils.ValueUtils.getValueAsInteger;
import static com.kingsrook.qqq.backend.core.utils.ValueUtils.getValueAsString;
/*******************************************************************************
** Abstract base class in hierarchy of classes that know how to construct &
** populate QMetaDataObject instances, based on input streams (e.g., from files).
*******************************************************************************/
public abstract class AbstractMetaDataLoader<T extends QMetaDataObject>
{
private static final QLogger LOG = QLogger.getLogger(AbstractMetaDataLoader.class);
private String fileName;
private List<LoadingProblem> problems = new ArrayList<>();
/***************************************************************************
**
***************************************************************************/
public T fileToMetaDataObject(QInstance qInstance, InputStream inputStream, String fileName) throws QMetaDataLoaderException
{
this.fileName = fileName;
Map<String, Object> map = fileToMap(inputStream, fileName);
LoadingContext loadingContext = new LoadingContext(fileName, "/");
return (mapToMetaDataObject(qInstance, map, loadingContext));
}
/***************************************************************************
**
***************************************************************************/
public abstract T mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException;
/***************************************************************************
**
***************************************************************************/
protected Map<String, Object> fileToMap(InputStream inputStream, String fileName) throws QMetaDataLoaderException
{
try
{
String string = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
string = StringUtils.ltrim(string);
if(fileName.toLowerCase().endsWith(".json"))
{
return JsonUtils.toObject(string, new TypeReference<>() {});
}
else if(fileName.toLowerCase().endsWith(".yaml") || fileName.toLowerCase().endsWith(".yml"))
{
return YamlUtils.toMap(string);
}
throw (new QMetaDataLoaderException("Unsupported file format (based on file name: " + fileName + ")"));
}
catch(IOException e)
{
throw new QMetaDataLoaderException("Error building map from file: " + fileName, e);
}
}
/***************************************************************************
*
***************************************************************************/
protected void reflectivelyMap(QInstance qInstance, QMetaDataObject targetObject, Map<String, Object> map, LoadingContext context)
{
Class<? extends QMetaDataObject> targetClass = targetObject.getClass();
Set<String> usedFieldNames = new HashSet<>();
for(Method method : targetClass.getMethods())
{
try
{
if(method.getName().startsWith("set") && method.getParameterTypes().length == 1)
{
String propertyName = StringUtils.lcFirst(method.getName().substring(3));
if(map.containsKey(propertyName))
{
usedFieldNames.add(propertyName);
Class<?> parameterType = method.getParameterTypes()[0];
Object rawValue = map.get(propertyName);
try
{
Object mappedValue = reflectivelyMapValue(qInstance, method, parameterType, rawValue, context.descendToProperty(propertyName));
method.invoke(targetObject, mappedValue);
}
catch(NoValueException nve)
{
///////////////////////
// don't call setter //
///////////////////////
LOG.debug("at " + context + ": No value was mapped for property [" + propertyName + "] on " + targetClass.getSimpleName() + "." + method.getName() + ", raw value: [" + rawValue + "]");
}
}
}
}
catch(Exception e)
{
addProblem(new LoadingProblem(context, "Error reflectively mapping on " + targetClass.getName() + "." + method.getName(), e));
}
}
//////////////////////////
// mmm, slightly sus... //
//////////////////////////
map.remove("class");
map.remove("version");
Set<String> unrecognizedKeys = new HashSet<>(map.keySet());
unrecognizedKeys.removeAll(usedFieldNames);
if(!unrecognizedKeys.isEmpty())
{
addProblem(new LoadingProblem(context, unrecognizedKeys.size() + " Unrecognized " + StringUtils.plural(unrecognizedKeys, "property", "properties") + ": " + unrecognizedKeys));
}
}
/***************************************************************************
*
***************************************************************************/
public Object reflectivelyMapValue(QInstance qInstance, Method method, Class<?> parameterType, Object rawValue, LoadingContext context) throws Exception
{
if(rawValue instanceof String s && s.matches("^\\$\\{.+\\..+}"))
{
rawValue = new QMetaDataVariableInterpreter().interpret(s);
LOG.debug("Interpreted raw value [" + s + "] as [" + StringUtils.maskAndTruncate(ValueUtils.getValueAsString(rawValue) + "]"));
}
if(parameterType.equals(String.class))
{
return (getValueAsString(rawValue));
}
else if(parameterType.equals(Integer.class))
{
try
{
return (getValueAsInteger(rawValue));
}
catch(Exception e)
{
addProblem(new LoadingProblem(context, "[" + rawValue + "] is not an Integer value."));
}
}
else if(parameterType.equals(Boolean.class))
{
if("true".equals(rawValue) || Boolean.TRUE.equals(rawValue))
{
return (true);
}
else if("false".equals(rawValue) || Boolean.FALSE.equals(rawValue))
{
return (false);
}
else if(rawValue == null)
{
return (null);
}
else
{
addProblem(new LoadingProblem(context, "[" + rawValue + "] is not a boolean value (must be 'true' or 'false')."));
return (null);
}
}
else if(parameterType.equals(boolean.class))
{
if("true".equals(rawValue) || Boolean.TRUE.equals(rawValue))
{
return (true);
}
else if("false".equals(rawValue) || Boolean.FALSE.equals(rawValue))
{
return (false);
}
else
{
addProblem(new LoadingProblem(context, rawValue + " is not a boolean value (must be 'true' or 'false')."));
throw (new NoValueException());
}
}
else if(parameterType.equals(List.class))
{
Type actualTypeArgument = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
Class<?> actualTypeClass = Class.forName(actualTypeArgument.getTypeName());
if(rawValue instanceof @SuppressWarnings("rawtypes")List valueList)
{
List<Object> mappedValueList = new ArrayList<>();
for(Object o : valueList)
{
try
{
Object mappedValue = reflectivelyMapValue(qInstance, null, actualTypeClass, o, context);
mappedValueList.add(mappedValue);
}
catch(NoValueException nve)
{
// leave off list
}
}
return (mappedValueList);
}
}
else if(parameterType.equals(Set.class))
{
Type actualTypeArgument = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
Class<?> actualTypeClass = Class.forName(actualTypeArgument.getTypeName());
if(rawValue instanceof @SuppressWarnings("rawtypes")List valueList)
{
Set<Object> mappedValueSet = new LinkedHashSet<>();
for(Object o : valueList)
{
try
{
Object mappedValue = reflectivelyMapValue(qInstance, null, actualTypeClass, o, context);
mappedValueSet.add(mappedValue);
}
catch(NoValueException nve)
{
// leave off list
}
}
return (mappedValueSet);
}
}
else if(parameterType.equals(Map.class))
{
Type keyType = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
if(!keyType.equals(String.class))
{
addProblem(new LoadingProblem(context, "Unsupported key type for " + method + " got [" + keyType + "], expected [String]"));
throw new NoValueException();
}
// todo make sure string
Type actualTypeArgument = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[1];
Class<?> actualTypeClass = Class.forName(actualTypeArgument.getTypeName());
if(rawValue instanceof @SuppressWarnings("rawtypes")Map valueMap)
{
Map<String, Object> mappedValueMap = new LinkedHashMap<>();
for(Object o : valueMap.entrySet())
{
try
{
@SuppressWarnings("unchecked")
Map.Entry<String, Object> entry = (Map.Entry<String, Object>) o;
Object mappedValue = reflectivelyMapValue(qInstance, null, actualTypeClass, entry.getValue(), context);
mappedValueMap.put(entry.getKey(), mappedValue);
}
catch(NoValueException nve)
{
// leave out of map
}
}
return (mappedValueMap);
}
}
else if(parameterType.isEnum())
{
String value = getValueAsString(rawValue);
for(Object enumConstant : parameterType.getEnumConstants())
{
if(((Enum<?>) enumConstant).name().equals(value))
{
return (enumConstant);
}
}
addProblem(new LoadingProblem(context, "Unrecognized value [" + rawValue + "]. Expected one of: " + Arrays.toString(parameterType.getEnumConstants())));
}
else if(MetaDataLoaderRegistry.hasLoaderForClass(parameterType))
{
if(rawValue instanceof @SuppressWarnings("rawtypes")Map valueMap)
{
Class<? extends AbstractMetaDataLoader<?>> loaderClass = MetaDataLoaderRegistry.getLoaderForClass(parameterType);
AbstractMetaDataLoader<?> loader = loaderClass.getConstructor().newInstance();
//noinspection unchecked
return (loader.mapToMetaDataObject(qInstance, valueMap, context));
}
}
else if(QMetaDataObject.class.isAssignableFrom(parameterType))
{
if(rawValue instanceof @SuppressWarnings("rawtypes")Map valueMap)
{
QMetaDataObject childObject = (QMetaDataObject) parameterType.getConstructor().newInstance();
//noinspection unchecked
reflectivelyMap(qInstance, childObject, valueMap, context);
return (childObject);
}
}
else if(parameterType.equals(Serializable.class))
{
if(rawValue instanceof String
|| rawValue instanceof Integer
|| rawValue instanceof BigDecimal
|| rawValue instanceof Boolean
)
{
return rawValue;
}
}
else
{
// todo clean up this message/level
addProblem(new LoadingProblem(context, "No case for " + parameterType + " (arg to: " + method + ")"));
}
throw new NoValueException();
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// unclear if the below is needed. if so, useful to not re-write, but is hurting test coverage, so zombie until used //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///***************************************************************************
// *
// ***************************************************************************/
//protected ListOfMapOrMapOfMap getListOfMapOrMapOfMap(Map<String, Object> map, String key)
//{
// if(map.containsKey(key))
// {
// if(map.get(key) instanceof List)
// {
// return (new ListOfMapOrMapOfMap((List<Map<String, Object>>) map.get(key)));
// }
// else if(map.get(key) instanceof Map)
// {
// return (new ListOfMapOrMapOfMap((Map<String, Map<String, Object>>) map.get(key)));
// }
// else
// {
// LOG.warn("Expected list or map under key [" + key + "] while processing [" + getClass().getSimpleName() + "] from [" + fileName + "], but found: " + (map.get(key) == null ? "null" : map.get(key).getClass().getSimpleName()));
// }
// }
// return (null);
//}
///***************************************************************************
// *
// ***************************************************************************/
//protected List<Map<String, Object>> getListOfMap(Map<String, Object> map, String key)
//{
// if(map.containsKey(key))
// {
// if(map.get(key) instanceof List)
// {
// return (List<Map<String, Object>>) map.get(key);
// }
// else
// {
// LOG.warn("Expected list under key [" + key + "] while processing [" + getClass().getSimpleName() + "] from [" + fileName + "], but found: " + (map.get(key) == null ? "null" : map.get(key).getClass().getSimpleName()));
// }
// }
// return (null);
//}
///***************************************************************************
// *
// ***************************************************************************/
//protected Map<String, Map<String, Object>> getMapOfMap(Map<String, Object> map, String key)
//{
// if(map.containsKey(key))
// {
// if(map.get(key) instanceof Map)
// {
// return (Map<String, Map<String, Object>>) map.get(key);
// }
// else
// {
// LOG.warn("Expected map under key [" + key + "] while processing [" + getClass().getSimpleName() + "] from [" + fileName + "], but found: " + (map.get(key) == null ? "null" : map.get(key).getClass().getSimpleName()));
// }
// }
// return (null);
//}
///***************************************************************************
// **
// ***************************************************************************/
//protected record ListOfMapOrMapOfMap(List<Map<String, Object>> listOf, Map<String, Map<String, Object>> mapOf)
//{
// /*******************************************************************************
// ** Constructor
// **
// *******************************************************************************/
// public ListOfMapOrMapOfMap(List<Map<String, Object>> listOf)
// {
// this(listOf, null);
// }
// /*******************************************************************************
// ** Constructor
// **
// *******************************************************************************/
// public ListOfMapOrMapOfMap(Map<String, Map<String, Object>> mapOf)
// {
// this(null, mapOf);
// }
//}
/*******************************************************************************
** Getter for fileName
**
*******************************************************************************/
public String getFileName()
{
return fileName;
}
/***************************************************************************
**
***************************************************************************/
private static class NoValueException extends Exception
{
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public NoValueException()
{
super("No value");
}
}
/***************************************************************************
**
***************************************************************************/
public void addProblem(LoadingProblem problem)
{
problems.add(problem);
}
/*******************************************************************************
** Getter for problems
**
*******************************************************************************/
public List<LoadingProblem> getProblems()
{
return (problems);
}
}

View File

@ -1,120 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.instances.loaders;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.instances.loaders.implementations.GenericMetaDataLoader;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.core.utils.memoization.AnyKey;
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
/*******************************************************************************
** Generic implementation of AbstractMetaDataLoader, who "detects" the class
** of meta data object to be created, then defers to an appropriate subclass
** to do the work.
*******************************************************************************/
public class ClassDetectingMetaDataLoader extends AbstractMetaDataLoader<QMetaDataObject>
{
private static final Memoization<AnyKey, List<Class<?>>> memoizedMetaDataObjectClasses = new Memoization<>();
/***************************************************************************
*
***************************************************************************/
public AbstractMetaDataLoader<?> getLoaderForFile(InputStream inputStream, String fileName) throws QMetaDataLoaderException
{
Map<String, Object> map = fileToMap(inputStream, fileName);
return (getLoaderForMap(map));
}
/***************************************************************************
*
***************************************************************************/
public AbstractMetaDataLoader<?> getLoaderForMap(Map<String, Object> map) throws QMetaDataLoaderException
{
if(map.containsKey("class"))
{
String classProperty = ValueUtils.getValueAsString(map.get("class"));
try
{
if(MetaDataLoaderRegistry.hasLoaderForSimpleName(classProperty))
{
Class<? extends AbstractMetaDataLoader<?>> loaderClass = MetaDataLoaderRegistry.getLoaderForSimpleName(classProperty);
return (loaderClass.getConstructor().newInstance());
}
else
{
Optional<List<Class<?>>> metaDataClasses = memoizedMetaDataObjectClasses.getResult(AnyKey.getInstance(), k -> ClassPathUtils.getClassesContainingNameAndOfType("MetaData", QMetaDataObject.class));
if(metaDataClasses.isEmpty())
{
throw (new QMetaDataLoaderException("Could not get list of metaDataObjects from class loader"));
}
for(Class<?> c : metaDataClasses.get())
{
if(c.getSimpleName().equals(classProperty) && QMetaDataObject.class.isAssignableFrom(c))
{
@SuppressWarnings("unchecked")
Class<? extends QMetaDataObject> metaDataClass = (Class<? extends QMetaDataObject>) c;
return new GenericMetaDataLoader<>(metaDataClass);
}
}
}
throw new QMetaDataLoaderException("Unexpected class [" + classProperty + "] (not a QMetaDataObject; doesn't have a registered MetaDataLoader) specified in " + getFileName());
}
catch(QMetaDataLoaderException qmdle)
{
throw (qmdle);
}
catch(Exception e)
{
throw new QMetaDataLoaderException("Error handling class [" + classProperty + "] specified in " + getFileName(), e);
}
}
else
{
throw new QMetaDataLoaderException("Cannot detect meta-data type, because [class] attribute was not specified in file: " + getFileName());
}
}
/***************************************************************************
**
***************************************************************************/
@Override
public QMetaDataObject mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
{
AbstractMetaDataLoader<?> loaderForMap = getLoaderForMap(map);
return loaderForMap.mapToMetaDataObject(qInstance, map, context);
}
}

View File

@ -1,38 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances.loaders;
/*******************************************************************************
** Record to track where loader objects are - e.g., what file they're on,
** and at what property path within the file (e.g., helps report problems).
*******************************************************************************/
public record LoadingContext(String fileName, String propertyPath)
{
/***************************************************************************
**
***************************************************************************/
public LoadingContext descendToProperty(String propertyName)
{
return new LoadingContext(fileName, propertyPath + propertyName + "/");
}
}

View File

@ -1,49 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances.loaders;
/*******************************************************************************
** record that tracks a problem that was encountered when loading files.
*******************************************************************************/
public record LoadingProblem(LoadingContext context, String message, Exception exception) // todo Level if useful
{
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public LoadingProblem(LoadingContext context, String message)
{
this(context, message, null);
}
/***************************************************************************
**
***************************************************************************/
@Override
public String toString()
{
return "at[" + context.fileName() + "][" + context.propertyPath() + "]: " + message;
}
}

View File

@ -1,118 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.instances.loaders;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.Pair;
/*******************************************************************************
** class that loads a directory full of meta data files into meta data objects,
** and then sets all of them in a QInstance.
*******************************************************************************/
public class MetaDataLoaderHelper
{
private static final QLogger LOG = QLogger.getLogger(MetaDataLoaderHelper.class);
/***************************************************************************
*
***************************************************************************/
public static void processAllMetaDataFilesInDirectory(QInstance qInstance, String path) throws QException
{
List<Pair<File, AbstractMetaDataLoader<?>>> loaders = new ArrayList<>();
File directory = new File(path);
processAllMetaDataFilesInDirectory(loaders, directory);
// todo - some version of sorting the loaders by type or possibly a sort field within the files (or file names)
for(Pair<File, AbstractMetaDataLoader<?>> pair : loaders)
{
File file = pair.getA();
AbstractMetaDataLoader<?> loader = pair.getB();
try(FileInputStream fileInputStream = new FileInputStream(file))
{
QMetaDataObject qMetaDataObject = loader.fileToMetaDataObject(qInstance, fileInputStream, file.getName());
if(CollectionUtils.nullSafeHasContents(loader.getProblems()))
{
loader.getProblems().forEach(System.out::println);
}
if(qMetaDataObject instanceof TopLevelMetaDataInterface topLevelMetaData)
{
topLevelMetaData.addSelfToInstance(qInstance);
}
else
{
LOG.warn("Received a non-topLevelMetaDataObject from file: " + file.getAbsolutePath());
}
}
catch(Exception e)
{
LOG.error("Error processing file: " + file.getAbsolutePath(), e);
}
}
}
/***************************************************************************
*
***************************************************************************/
private static void processAllMetaDataFilesInDirectory(List<Pair<File, AbstractMetaDataLoader<?>>> loaders, File directory) throws QException
{
for(File file : Objects.requireNonNullElse(directory.listFiles(), new File[0]))
{
if(file.isDirectory())
{
processAllMetaDataFilesInDirectory(loaders, file);
}
else
{
try(FileInputStream fileInputStream = new FileInputStream(file))
{
AbstractMetaDataLoader<?> loader = new ClassDetectingMetaDataLoader().getLoaderForFile(fileInputStream, file.getName());
loaders.add(Pair.of(file, loader));
}
catch(Exception e)
{
LOG.error("Error processing file: " + file.getAbsolutePath(), e);
}
}
}
}
}

View File

@ -1,120 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances.loaders;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.instances.loaders.implementations.QTableMetaDataLoader;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
/*******************************************************************************
**
*******************************************************************************/
public class MetaDataLoaderRegistry
{
private static final QLogger LOG = QLogger.getLogger(AbstractMetaDataLoader.class);
private static final Map<Class<?>, Class<? extends AbstractMetaDataLoader<?>>> registeredLoaders = new HashMap<>();
private static final Map<String, Class<? extends AbstractMetaDataLoader<?>>> registeredLoadersByTargetSimpleName = new HashMap<>();
static
{
try
{
List<Class<?>> classesInPackage = ClassPathUtils.getClassesInPackage(QTableMetaDataLoader.class.getPackageName());
for(Class<?> possibleLoaderClass : classesInPackage)
{
try
{
Type superClass = possibleLoaderClass.getGenericSuperclass();
if(superClass.getTypeName().startsWith(AbstractMetaDataLoader.class.getName() + "<"))
{
Type actualTypeArgument = ((ParameterizedType) superClass).getActualTypeArguments()[0];
if(actualTypeArgument instanceof Class)
{
//noinspection unchecked
Class<? extends AbstractMetaDataLoader<?>> loaderClass = (Class<? extends AbstractMetaDataLoader<?>>) possibleLoaderClass;
Class<?> metaDataObjectType = Class.forName(actualTypeArgument.getTypeName());
registeredLoaders.put(metaDataObjectType, loaderClass);
registeredLoadersByTargetSimpleName.put(metaDataObjectType.getSimpleName(), loaderClass);
}
}
}
catch(Exception e)
{
LOG.info("Error on class: " + possibleLoaderClass, e);
}
}
System.out.println("Registered loaders: " + registeredLoadersByTargetSimpleName);
}
catch(Exception e)
{
LOG.error("Error in static init block for MetaDataLoaderRegistry", e);
}
}
/***************************************************************************
**
***************************************************************************/
public static boolean hasLoaderForClass(Class<?> metaDataClass)
{
return registeredLoaders.containsKey(metaDataClass);
}
/***************************************************************************
**
***************************************************************************/
public static Class<? extends AbstractMetaDataLoader<?>> getLoaderForClass(Class<?> metaDataClass)
{
return registeredLoaders.get(metaDataClass);
}
/***************************************************************************
**
***************************************************************************/
public static boolean hasLoaderForSimpleName(String targetSimpleName)
{
return registeredLoadersByTargetSimpleName.containsKey(targetSimpleName);
}
/***************************************************************************
**
***************************************************************************/
public static Class<? extends AbstractMetaDataLoader<?>> getLoaderForSimpleName(String targetSimpleName)
{
return registeredLoadersByTargetSimpleName.get(targetSimpleName);
}
}

View File

@ -1,50 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.instances.loaders;
/*******************************************************************************
**
*******************************************************************************/
public class QMetaDataLoaderException extends Exception
{
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public QMetaDataLoaderException(String message)
{
super(message);
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public QMetaDataLoaderException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@ -1,71 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.instances.loaders.implementations;
import java.util.Map;
import com.kingsrook.qqq.backend.core.instances.loaders.AbstractMetaDataLoader;
import com.kingsrook.qqq.backend.core.instances.loaders.LoadingContext;
import com.kingsrook.qqq.backend.core.instances.loaders.QMetaDataLoaderException;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
/*******************************************************************************
**
*******************************************************************************/
public class GenericMetaDataLoader<T extends QMetaDataObject> extends AbstractMetaDataLoader<T>
{
private final Class<T> metaDataClass;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public GenericMetaDataLoader(Class<T> metaDataClass)
{
this.metaDataClass = metaDataClass;
}
/***************************************************************************
**
***************************************************************************/
@Override
public T mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
{
try
{
T object = metaDataClass.getConstructor().newInstance();
reflectivelyMap(qInstance, object, map, context);
return (object);
}
catch(Exception e)
{
throw (new QMetaDataLoaderException("Error loading metaData object of type " + metaDataClass.getSimpleName(), e));
}
}
}

View File

@ -1,85 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.instances.loaders.implementations;
import java.util.Map;
import com.kingsrook.qqq.backend.core.instances.loaders.AbstractMetaDataLoader;
import com.kingsrook.qqq.backend.core.instances.loaders.LoadingContext;
import com.kingsrook.qqq.backend.core.instances.loaders.QMetaDataLoaderException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
**
*******************************************************************************/
public class QStepDataLoader extends AbstractMetaDataLoader<QStepMetaData>
{
private static final QLogger LOG = QLogger.getLogger(QStepDataLoader.class);
/***************************************************************************
**
***************************************************************************/
@Override
public QStepMetaData mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
{
String stepType = ValueUtils.getValueAsString(map.get("stepType"));
if(!StringUtils.hasContent(stepType))
{
throw (new QMetaDataLoaderException("stepType was not specified for process step"));
}
QStepMetaData step;
if("backend".equalsIgnoreCase(stepType))
{
step = new QBackendStepMetaData();
reflectivelyMap(qInstance, step, map, context);
}
else if("frontend".equalsIgnoreCase(stepType))
{
step = new QFrontendStepMetaData();
reflectivelyMap(qInstance, step, map, context);
}
// todo - we have custom factory methods for this, so, maybe needs all custom loader?
// else if("stateMachine".equalsIgnoreCase(stepType))
// {
// step = new QStateMachineStep();
// reflectivelyMap(qInstance, step, map, context);
// }
else
{
throw (new QMetaDataLoaderException("Unsupported step stepType: " + stepType));
}
return (step);
}
}

View File

@ -1,58 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.instances.loaders.implementations;
import java.util.Map;
import com.kingsrook.qqq.backend.core.instances.loaders.AbstractMetaDataLoader;
import com.kingsrook.qqq.backend.core.instances.loaders.LoadingContext;
import com.kingsrook.qqq.backend.core.instances.loaders.QMetaDataLoaderException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
**
*******************************************************************************/
public class QTableMetaDataLoader extends AbstractMetaDataLoader<QTableMetaData>
{
private static final QLogger LOG = QLogger.getLogger(QTableMetaDataLoader.class);
/***************************************************************************
**
***************************************************************************/
@Override
public QTableMetaData mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
{
QTableMetaData table = new QTableMetaData();
reflectivelyMap(qInstance, table, map, context);
// todo - handle QTableBackendDetails, based on backend's type
return (table);
}
}

View File

@ -38,13 +38,4 @@ public interface QInstanceValidatorPluginInterface<T>
*******************************************************************************/
void validate(T object, QInstance qInstance, QInstanceValidator qInstanceValidator);
/***************************************************************************
**
***************************************************************************/
default String getPluginIdentifier()
{
return getClass().getName();
}
}

View File

@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.model.actions.audits;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
@ -37,8 +36,6 @@ public class AuditInput extends AbstractActionInput implements Serializable
{
private List<AuditSingleInput> auditSingleInputList = new ArrayList<>();
private QBackendTransaction transaction;
/*******************************************************************************
@ -95,42 +92,4 @@ public class AuditInput extends AbstractActionInput implements Serializable
return (this);
}
/*******************************************************************************
* Getter for transaction
* @see #withTransaction(QBackendTransaction)
*******************************************************************************/
public QBackendTransaction getTransaction()
{
return (this.transaction);
}
/*******************************************************************************
* Setter for transaction
* @see #withTransaction(QBackendTransaction)
*******************************************************************************/
public void setTransaction(QBackendTransaction transaction)
{
this.transaction = transaction;
}
/*******************************************************************************
* Fluent setter for transaction
*
* @param transaction
* transaction upon which the audits will be inserted.
*
* @return this
*******************************************************************************/
public AuditInput withTransaction(QBackendTransaction transaction)
{
this.transaction = transaction;
return (this);
}
}

View File

@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.core.model.actions.audits;
import java.io.Serializable;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -39,8 +38,6 @@ public class DMLAuditInput extends AbstractActionInput implements Serializable
private List<QRecord> oldRecordList;
private AbstractTableActionInput tableActionInput;
private QBackendTransaction transaction;
private String auditContext = null;
@ -167,43 +164,4 @@ public class DMLAuditInput extends AbstractActionInput implements Serializable
return (this);
}
/*******************************************************************************
* Getter for transaction
* @see #withTransaction(QBackendTransaction)
*******************************************************************************/
public QBackendTransaction getTransaction()
{
return (this.transaction);
}
/*******************************************************************************
* Setter for transaction
* @see #withTransaction(QBackendTransaction)
*******************************************************************************/
public void setTransaction(QBackendTransaction transaction)
{
this.transaction = transaction;
}
/*******************************************************************************
* Fluent setter for transaction
*
* @param transaction
* transaction that will be used for inserting the audits, where (presumably)
* the DML against the record occurred as well
*
* @return this
*******************************************************************************/
public DMLAuditInput withTransaction(QBackendTransaction transaction)
{
this.transaction = transaction;
return (this);
}
}

View File

@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.model.actions.metadata;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
@ -42,13 +41,12 @@ import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
*******************************************************************************/
public class MetaDataOutput extends AbstractActionOutput
{
private Map<String, QFrontendTableMetaData> tables;
private Map<String, QFrontendProcessMetaData> processes;
private Map<String, QFrontendReportMetaData> reports;
private Map<String, QFrontendAppMetaData> apps;
private Map<String, QFrontendWidgetMetaData> widgets;
private Map<String, String> environmentValues;
private Map<String, QSupplementalInstanceMetaData> supplementalInstanceMetaData;
private Map<String, QFrontendTableMetaData> tables;
private Map<String, QFrontendProcessMetaData> processes;
private Map<String, QFrontendReportMetaData> reports;
private Map<String, QFrontendAppMetaData> apps;
private Map<String, QFrontendWidgetMetaData> widgets;
private Map<String, String> environmentValues;
private List<AppTreeNode> appTree;
private QBrandingMetaData branding;
@ -232,28 +230,6 @@ public class MetaDataOutput extends AbstractActionOutput
/*******************************************************************************
** Getter for supplementalInstanceMetaData
**
*******************************************************************************/
public Map<String, QSupplementalInstanceMetaData> getSupplementalInstanceMetaData()
{
return supplementalInstanceMetaData;
}
/*******************************************************************************
** Setter for supplementalInstanceMetaData
**
*******************************************************************************/
public void setSupplementalInstanceMetaData(Map<String, QSupplementalInstanceMetaData> supplementalInstanceMetaData)
{
this.supplementalInstanceMetaData = supplementalInstanceMetaData;
}
/*******************************************************************************
** Setter for helpContents
**

View File

@ -23,11 +23,7 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.logging.LogPair;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
@ -35,45 +31,6 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
*******************************************************************************/
public interface ProcessSummaryLineInterface extends Serializable
{
QLogger LOG = QLogger.getLogger(ProcessSummaryLineInterface.class);
/***************************************************************************
**
***************************************************************************/
static void log(String message, Serializable summaryLines, List<LogPair> additionalLogPairs)
{
try
{
if(summaryLines instanceof List)
{
List<ProcessSummaryLineInterface> list = (List<ProcessSummaryLineInterface>) summaryLines;
List<LogPair> logPairs = new ArrayList<>();
for(ProcessSummaryLineInterface processSummaryLineInterface : list)
{
LogPair logPair = processSummaryLineInterface.toLogPair();
logPair.setKey(logPair.getKey() + logPairs.size());
logPairs.add(logPair);
}
if(additionalLogPairs != null)
{
logPairs.addAll(0, additionalLogPairs);
}
logPairs.add(0, logPair("message", message));
LOG.info(logPairs);
}
else
{
LOG.info("Unrecognized type for summaryLines (expected List)", logPair("processSummary", summaryLines));
}
}
catch(Exception e)
{
LOG.info("Error logging a process summary", e, logPair("processSummary", summaryLines));
}
}
/*******************************************************************************
** Getter for status

View File

@ -1,162 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
/*******************************************************************************
** base-class for bean-like classes to represent the fields of a process.
** similar in spirit to QRecordEntity, but for processes.
*******************************************************************************/
public class QProcessPayload
{
private static final QLogger LOG = QLogger.getLogger(QProcessPayload.class);
private static final ListingHash<Class<? extends QProcessPayload>, QRecordEntityField> fieldMapping = new ListingHash<>();
/*******************************************************************************
** Build an entity of this QRecord type from a QRecord
**
*******************************************************************************/
public static <T extends QProcessPayload> T fromProcessState(Class<T> c, ProcessState processState) throws QException
{
try
{
T entity = c.getConstructor().newInstance();
entity.populateFromProcessState(processState);
return (entity);
}
catch(Exception e)
{
throw (new QException("Error building process payload from state.", e));
}
}
/***************************************************************************
**
***************************************************************************/
protected void populateFromProcessState(ProcessState processState)
{
try
{
List<QRecordEntityField> fieldList = getFieldList(this.getClass());
for(QRecordEntityField qRecordEntityField : fieldList)
{
Serializable value = processState.getValues().get(qRecordEntityField.getFieldName());
Object typedValue = qRecordEntityField.convertValueType(value);
qRecordEntityField.getSetter().invoke(this, typedValue);
}
}
catch(Exception e)
{
throw (new QRuntimeException("Error building process payload from process state.", e));
}
}
/*******************************************************************************
** Copy the values from this payload into the given process state.
** ALL fields in the entity will be set in the process state.
**
*******************************************************************************/
public void toProcessState(ProcessState processState) throws QRuntimeException
{
try
{
for(QRecordEntityField qRecordEntityField : getFieldList(this.getClass()))
{
processState.getValues().put(qRecordEntityField.getFieldName(), (Serializable) qRecordEntityField.getGetter().invoke(this));
}
}
catch(Exception e)
{
throw (new QRuntimeException("Error populating process state from process payload.", e));
}
}
/***************************************************************************
*
***************************************************************************/
public static Set<Class<?>> allowedFieldTypes()
{
HashSet<Class<?>> classes = new HashSet<>(ReflectiveBeanLikeClassUtils.defaultAllowedTypes());
classes.add(Map.class);
classes.add(List.class);
return (classes);
}
/*******************************************************************************
**
*******************************************************************************/
public static List<QRecordEntityField> getFieldList(Class<? extends QProcessPayload> c)
{
if(!fieldMapping.containsKey(c))
{
List<QRecordEntityField> fieldList = new ArrayList<>();
for(Method possibleGetter : c.getMethods())
{
if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, false, allowedFieldTypes()))
{
Optional<Method> setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
if(setter.isPresent())
{
String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
fieldList.add(new QRecordEntityField(fieldName, possibleGetter, setter.get(), possibleGetter.getReturnType(), null));
}
else
{
LOG.debug("Getter method [" + possibleGetter.getName() + "] does not have a corresponding setter.");
}
}
}
fieldMapping.put(c, fieldList);
}
return (fieldMapping.get(c));
}
}

View File

@ -628,15 +628,4 @@ public class RunBackendStepInput extends AbstractActionInput
{
return (QContext.getQInstance().getProcess(getProcessName()));
}
/***************************************************************************
** return a QProcessPayload subclass instance, with values populated from
** the current process state.
***************************************************************************/
public <T extends QProcessPayload> T getProcessPayload(Class<T> payloadClass) throws QException
{
return QProcessPayload.fromProcessState(payloadClass, getProcessState());
}
}

View File

@ -445,14 +445,4 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
this.processState.setProcessMetaDataAdjustment(processMetaDataAdjustment);
}
/***************************************************************************
** Update the process state with values from the input processPayload
** subclass instance.
***************************************************************************/
public void setProcessPayload(QProcessPayload processPayload)
{
processPayload.toProcessState(getProcessState());
}
}

View File

@ -1,35 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables;
import java.io.Serializable;
/*******************************************************************************
** interface to mark enums (presumably classes too, but the original intent is
** enums) that can be added to insert/update/delete action inputs to flag behaviors
*******************************************************************************/
public interface ActionFlag extends Serializable
{
}

View File

@ -1,117 +0,0 @@
/*
* 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.model.actions.tables;
import java.util.EnumSet;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
/*******************************************************************************
** Common getters & setters, shared by both QueryInput and CountInput.
**
** Original impetus for this class is the setCommonParamsFrom() method - for cases
** where we need to change a Query to a Get, or vice-versa, and we want to copy over
** all of those input params.
*******************************************************************************/
public interface QueryOrCountInputInterface
{
/*******************************************************************************
** Set in THIS, the "common params" (e.g., common to both Query & Count inputs)
** from the parameter SOURCE object.
*******************************************************************************/
default void setCommonParamsFrom(QueryOrCountInputInterface source)
{
this.setTransaction(source.getTransaction());
this.setFilter(source.getFilter());
this.setTableName(source.getTableName());
this.setQueryJoins(source.getQueryJoins());
this.setTimeoutSeconds(source.getTimeoutSeconds());
this.setQueryHints(source.getQueryHints());
}
/*******************************************************************************
**
*******************************************************************************/
String getTableName();
/***************************************************************************
**
***************************************************************************/
void setTableName(String tableName);
/*******************************************************************************
**
*******************************************************************************/
QQueryFilter getFilter();
/***************************************************************************
**
***************************************************************************/
void setFilter(QQueryFilter filter);
/*******************************************************************************
** Getter for transaction
*******************************************************************************/
QBackendTransaction getTransaction();
/*******************************************************************************
** Setter for transaction
*******************************************************************************/
void setTransaction(QBackendTransaction transaction);
/*******************************************************************************
** Getter for queryJoins
*******************************************************************************/
List<QueryJoin> getQueryJoins();
/*******************************************************************************
** Setter for queryJoins
**
*******************************************************************************/
void setQueryJoins(List<QueryJoin> queryJoins);
/*******************************************************************************
**
*******************************************************************************/
Integer getTimeoutSeconds();
/***************************************************************************
**
***************************************************************************/
void setTimeoutSeconds(Integer timeoutSeconds);
/*******************************************************************************
** Getter for queryHints
*******************************************************************************/
EnumSet<QueryHint> getQueryHints();
/*******************************************************************************
** Setter for queryHints
*******************************************************************************/
void setQueryHints(EnumSet<QueryHint> queryHints);
}

View File

@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.aggregate;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@ -38,8 +37,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
*******************************************************************************/
public class AggregateInput extends AbstractTableActionInput
{
private QBackendTransaction transaction;
private QQueryFilter filter;
private List<Aggregate> aggregates;
private List<GroupBy> groupBys = new ArrayList<>();
@ -407,35 +404,4 @@ public class AggregateInput extends AbstractTableActionInput
return (queryHints.contains(queryHint));
}
/*******************************************************************************
** Getter for transaction
*******************************************************************************/
public QBackendTransaction getTransaction()
{
return (this.transaction);
}
/*******************************************************************************
** Setter for transaction
*******************************************************************************/
public void setTransaction(QBackendTransaction transaction)
{
this.transaction = transaction;
}
/*******************************************************************************
** Fluent setter for transaction
*******************************************************************************/
public AggregateInput withTransaction(QBackendTransaction transaction)
{
this.transaction = transaction;
return (this);
}
}

View File

@ -25,10 +25,8 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.count;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrCountInputInterface;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
@ -37,10 +35,9 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
** Input data for the Count action
**
*******************************************************************************/
public class CountInput extends AbstractTableActionInput implements QueryOrCountInputInterface
public class CountInput extends AbstractTableActionInput
{
private QBackendTransaction transaction;
private QQueryFilter filter;
private QQueryFilter filter;
private Integer timeoutSeconds;
@ -288,35 +285,4 @@ public class CountInput extends AbstractTableActionInput implements QueryOrCount
return (queryHints.contains(queryHint));
}
/*******************************************************************************
** Getter for transaction
*******************************************************************************/
public QBackendTransaction getTransaction()
{
return (this.transaction);
}
/*******************************************************************************
** Setter for transaction
*******************************************************************************/
public void setTransaction(QBackendTransaction transaction)
{
this.transaction = transaction;
}
/*******************************************************************************
** Fluent setter for transaction
*******************************************************************************/
public CountInput withTransaction(QBackendTransaction transaction)
{
this.transaction = transaction;
return (this);
}
}

View File

@ -24,12 +24,9 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.delete;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@ -50,8 +47,6 @@ public class DeleteInput extends AbstractTableActionInput
private boolean omitDmlAudit = false;
private String auditContext = null;
private Set<ActionFlag> flags;
/*******************************************************************************
@ -300,65 +295,4 @@ public class DeleteInput extends AbstractTableActionInput
return (this);
}
/*******************************************************************************
** Getter for flags
*******************************************************************************/
public Set<ActionFlag> getFlags()
{
return (this.flags);
}
/*******************************************************************************
** Setter for flags
*******************************************************************************/
public void setFlags(Set<ActionFlag> flags)
{
this.flags = flags;
}
/*******************************************************************************
** Fluent setter for flags
*******************************************************************************/
public DeleteInput withFlags(Set<ActionFlag> flags)
{
this.flags = flags;
return (this);
}
/***************************************************************************
**
***************************************************************************/
public DeleteInput withFlag(ActionFlag flag)
{
if(this.flags == null)
{
this.flags = new HashSet<>();
}
this.flags.add(flag);
return (this);
}
/***************************************************************************
**
***************************************************************************/
public boolean hasFlag(ActionFlag flag)
{
if(this.flags == null)
{
return (false);
}
return (this.flags.contains(flag));
}
}

View File

@ -23,12 +23,9 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.insert;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -51,8 +48,6 @@ public class InsertInput extends AbstractTableActionInput
private boolean omitDmlAudit = false;
private String auditContext = null;
private Set<ActionFlag> flags;
/*******************************************************************************
@ -321,65 +316,4 @@ public class InsertInput extends AbstractTableActionInput
return (this);
}
/*******************************************************************************
** Getter for flags
*******************************************************************************/
public Set<ActionFlag> getFlags()
{
return (this.flags);
}
/*******************************************************************************
** Setter for flags
*******************************************************************************/
public void setFlags(Set<ActionFlag> flags)
{
this.flags = flags;
}
/*******************************************************************************
** Fluent setter for flags
*******************************************************************************/
public InsertInput withFlags(Set<ActionFlag> flags)
{
this.flags = flags;
return (this);
}
/***************************************************************************
**
***************************************************************************/
public InsertInput withFlag(ActionFlag flag)
{
if(this.flags == null)
{
this.flags = new HashSet<>();
}
this.flags.add(flag);
return (this);
}
/***************************************************************************
**
***************************************************************************/
public boolean hasFlag(ActionFlag flag)
{
if(this.flags == null)
{
return (false);
}
return (this.flags.contains(flag));
}
}

View File

@ -33,7 +33,6 @@ import java.util.Set;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization.QFilterCriteriaDeserializer;
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@ -43,7 +42,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
*
*******************************************************************************/
@JsonDeserialize(using = QFilterCriteriaDeserializer.class)
public class QFilterCriteria implements Serializable, Cloneable, QMetaDataObject
public class QFilterCriteria implements Serializable, Cloneable
{
private static final QLogger LOG = QLogger.getLogger(QFilterCriteria.class);

View File

@ -23,14 +23,13 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
import java.io.Serializable;
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
/*******************************************************************************
** Bean representing an element of a query order-by clause.
**
*******************************************************************************/
public class QFilterOrderBy implements Serializable, Cloneable, QMetaDataObject
public class QFilterOrderBy implements Serializable, Cloneable
{
private String fieldName;
private boolean isAscending = true;

View File

@ -36,7 +36,6 @@ import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.FilterVariableExpression;
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -46,7 +45,7 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
* Full "filter" for a query - a list of criteria and order-bys
*
*******************************************************************************/
public class QQueryFilter implements Serializable, Cloneable, QMetaDataObject
public class QQueryFilter implements Serializable, Cloneable
{
private static final QLogger LOG = QLogger.getLogger(QQueryFilter.class);
@ -659,21 +658,6 @@ public class QQueryFilter implements Serializable, Cloneable, QMetaDataObject
}
}
//////////////////////////////////////
// recursively process sub filters! //
//////////////////////////////////////
for(QQueryFilter subFilter : CollectionUtils.nonNullList(getSubFilters()))
{
try
{
subFilter.interpretValues(inputValues, useCase);
}
catch(Exception e)
{
caughtExceptions.add(e);
}
}
if(!caughtExceptions.isEmpty())
{
String message = "Error interpreting filter values: " + StringUtils.joinWithCommasAndAnd(caughtExceptions.stream().map(e -> e.getMessage()).toList());
@ -839,7 +823,6 @@ public class QQueryFilter implements Serializable, Cloneable, QMetaDataObject
}
/*******************************************************************************
** Getter for subFilterSetOperator
*******************************************************************************/
@ -870,7 +853,6 @@ public class QQueryFilter implements Serializable, Cloneable, QMetaDataObject
}
/***************************************************************************
**
***************************************************************************/

View File

@ -32,7 +32,6 @@ import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrCountInputInterface;
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
@ -43,7 +42,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterf
** CountInput, and AggregateInput}, with common attributes for all of these
** "read" operations (like, queryHints,
*******************************************************************************/
public class QueryInput extends AbstractTableActionInput implements QueryOrGetInputInterface, QueryOrCountInputInterface, Cloneable
public class QueryInput extends AbstractTableActionInput implements QueryOrGetInputInterface, Cloneable
{
private QBackendTransaction transaction;
private QQueryFilter filter;

View File

@ -22,12 +22,9 @@
package com.kingsrook.qqq.backend.core.model.actions.tables.replace;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
@ -42,14 +39,12 @@ public class ReplaceInput extends AbstractTableActionInput
private UniqueKey key;
private List<QRecord> records;
private QQueryFilter filter;
private boolean performDeletes = true;
private boolean allowNullKeyValuesToEqual = false;
private boolean setPrimaryKeyInInsertedRecords = false;
private boolean performDeletes = true;
private boolean allowNullKeyValuesToEqual = false;
private boolean setPrimaryKeyInInsertedRecords = false;
private boolean omitDmlAudit = false;
private Set<ActionFlag> flags;
/*******************************************************************************
@ -308,65 +303,4 @@ public class ReplaceInput extends AbstractTableActionInput
return (this);
}
/*******************************************************************************
** Getter for flags
*******************************************************************************/
public Set<ActionFlag> getFlags()
{
return (this.flags);
}
/*******************************************************************************
** Setter for flags
*******************************************************************************/
public void setFlags(Set<ActionFlag> flags)
{
this.flags = flags;
}
/*******************************************************************************
** Fluent setter for flags
*******************************************************************************/
public ReplaceInput withFlags(Set<ActionFlag> flags)
{
this.flags = flags;
return (this);
}
/***************************************************************************
**
***************************************************************************/
public ReplaceInput withFlag(ActionFlag flag)
{
if(this.flags == null)
{
this.flags = new HashSet<>();
}
this.flags.add(flag);
return (this);
}
/***************************************************************************
**
***************************************************************************/
public boolean hasFlag(ActionFlag flag)
{
if(this.flags == null)
{
return (false);
}
return (this.flags.contains(flag));
}
}

View File

@ -23,12 +23,9 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.update;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
@ -59,8 +56,6 @@ public class UpdateInput extends AbstractTableActionInput
private boolean omitModifyDateUpdate = false;
private String auditContext = null;
private Set<ActionFlag> flags;
/*******************************************************************************
@ -390,65 +385,4 @@ public class UpdateInput extends AbstractTableActionInput
return (this);
}
/*******************************************************************************
** Getter for flags
*******************************************************************************/
public Set<ActionFlag> getFlags()
{
return (this.flags);
}
/*******************************************************************************
** Setter for flags
*******************************************************************************/
public void setFlags(Set<ActionFlag> flags)
{
this.flags = flags;
}
/*******************************************************************************
** Fluent setter for flags
*******************************************************************************/
public UpdateInput withFlags(Set<ActionFlag> flags)
{
this.flags = flags;
return (this);
}
/***************************************************************************
**
***************************************************************************/
public UpdateInput withFlag(ActionFlag flag)
{
if(this.flags == null)
{
this.flags = new HashSet<>();
}
this.flags.add(flag);
return (this);
}
/***************************************************************************
**
***************************************************************************/
public boolean hasFlag(ActionFlag flag)
{
if(this.flags == null)
{
return (false);
}
return (this.flags.contains(flag));
}
}

View File

@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.model.actions.values;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@ -41,8 +40,6 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput implemen
private List<Serializable> idList;
private List<String> labelList;
private Map<String, Serializable> otherValues;
private Integer skip = 0;
private Integer limit = 250;
@ -287,7 +284,6 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput implemen
}
/*******************************************************************************
** Getter for labelList
*******************************************************************************/
@ -317,35 +313,4 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput implemen
return (this);
}
/*******************************************************************************
** Getter for otherValues
*******************************************************************************/
public Map<String, Serializable> getOtherValues()
{
return (this.otherValues);
}
/*******************************************************************************
** Setter for otherValues
*******************************************************************************/
public void setOtherValues(Map<String, Serializable> otherValues)
{
this.otherValues = otherValues;
}
/*******************************************************************************
** Fluent setter for otherValues
*******************************************************************************/
public SearchPossibleValueSourceInput withOtherValues(Map<String, Serializable> otherValues)
{
this.otherValues = otherValues;
return (this);
}
}

View File

@ -27,7 +27,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TablesSupportingAutomationsPossibleValueSourceMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.metadata.tables.TablesPossibleValueSourceMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.savedviews.SavedView;
import com.kingsrook.qqq.backend.core.model.scripts.Script;
@ -48,16 +48,16 @@ public class TableTrigger extends QRecordEntity
@QField(isEditable = false)
private Instant modifyDate;
@QField(possibleValueSourceName = TablesSupportingAutomationsPossibleValueSourceMetaDataProvider.NAME, isRequired = true)
@QField(possibleValueSourceName = TablesPossibleValueSourceMetaDataProvider.NAME)
private String tableName;
@QField(possibleValueSourceName = SavedView.TABLE_NAME)
private Integer filterId;
@QField(possibleValueSourceName = Script.TABLE_NAME, isRequired = true)
@QField(possibleValueSourceName = Script.TABLE_NAME)
private Integer scriptId;
@QField(defaultValue = "500")
@QField()
private Integer priority;
@QField()

View File

@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.model.common;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.TimeZone;
import java.util.function.Function;
@ -48,7 +47,7 @@ public class TimeZonePossibleValueSourceMetaDataProvider
*******************************************************************************/
public QPossibleValueSource produce()
{
return (produce(null, null, null));
return (produce(null, null));
}
@ -57,16 +56,6 @@ public class TimeZonePossibleValueSourceMetaDataProvider
**
*******************************************************************************/
public QPossibleValueSource produce(Predicate<String> filter, Function<String, String> labelMapper)
{
return (produce(filter, labelMapper, null));
}
/*******************************************************************************
**
*******************************************************************************/
public QPossibleValueSource produce(Predicate<String> filter, Function<String, String> labelMapper, Comparator<QPossibleValue<?>> comparator)
{
QPossibleValueSource possibleValueSource = new QPossibleValueSource()
.withName("timeZones")
@ -83,11 +72,6 @@ public class TimeZonePossibleValueSourceMetaDataProvider
}
}
if(comparator != null)
{
enumValues.sort(comparator);
}
possibleValueSource.withEnumValues(enumValues);
return (possibleValueSource);
}

View File

@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
@ -53,7 +52,6 @@ public class ChildRecordListData extends QWidgetData
private Map<String, Serializable> defaultValuesForNewChildRecords;
private Set<String> disabledFieldsForNewChildRecords;
private Map<String, String> defaultValuesForNewChildRecordsFromParentFields;
private List<String> omitFieldNames;
@ -557,37 +555,6 @@ public class ChildRecordListData extends QWidgetData
return (this);
}
/*******************************************************************************
** Getter for omitFieldNames
*******************************************************************************/
public List<String> getOmitFieldNames()
{
return (this.omitFieldNames);
}
/*******************************************************************************
** Setter for omitFieldNames
*******************************************************************************/
public void setOmitFieldNames(List<String> omitFieldNames)
{
this.omitFieldNames = omitFieldNames;
}
/*******************************************************************************
** Fluent setter for omitFieldNames
*******************************************************************************/
public ChildRecordListData withOmitFieldNames(List<String> omitFieldNames)
{
this.omitFieldNames = omitFieldNames;
return (this);
}
}

View File

@ -35,15 +35,7 @@ public class FilterAndColumnsSetupData extends QWidgetData
private Boolean allowVariables = false;
private Boolean hideColumns = false;
private Boolean hidePreview = false;
private Boolean hideSortBy = false;
private Boolean overrideIsEditable;
private List<String> filterDefaultFieldNames;
private List<String> omitExposedJoins;
private Boolean isApiVersioned = false;
private String apiName;
private String apiPath;
private String apiVersion;
private String filterFieldName = "queryFilterJson";
private String columnFieldName = "columnsJson";
@ -298,227 +290,4 @@ public class FilterAndColumnsSetupData extends QWidgetData
return (this);
}
/*******************************************************************************
** Getter for overrideIsEditable
*******************************************************************************/
public Boolean getOverrideIsEditable()
{
return (this.overrideIsEditable);
}
/*******************************************************************************
** Setter for overrideIsEditable
*******************************************************************************/
public void setOverrideIsEditable(Boolean overrideIsEditable)
{
this.overrideIsEditable = overrideIsEditable;
}
/*******************************************************************************
** Fluent setter for overrideIsEditable
*******************************************************************************/
public FilterAndColumnsSetupData withOverrideIsEditable(Boolean overrideIsEditable)
{
this.overrideIsEditable = overrideIsEditable;
return (this);
}
/*******************************************************************************
** Getter for hideSortBy
*******************************************************************************/
public Boolean getHideSortBy()
{
return (this.hideSortBy);
}
/*******************************************************************************
** Setter for hideSortBy
*******************************************************************************/
public void setHideSortBy(Boolean hideSortBy)
{
this.hideSortBy = hideSortBy;
}
/*******************************************************************************
** Fluent setter for hideSortBy
*******************************************************************************/
public FilterAndColumnsSetupData withHideSortBy(Boolean hideSortBy)
{
this.hideSortBy = hideSortBy;
return (this);
}
/*******************************************************************************
** Getter for isApiVersioned
*******************************************************************************/
public Boolean getIsApiVersioned()
{
return (this.isApiVersioned);
}
/*******************************************************************************
** Setter for isApiVersioned
*******************************************************************************/
public void setIsApiVersioned(Boolean isApiVersioned)
{
this.isApiVersioned = isApiVersioned;
}
/*******************************************************************************
** Fluent setter for isApiVersioned
*******************************************************************************/
public FilterAndColumnsSetupData withIsApiVersioned(Boolean isApiVersioned)
{
this.isApiVersioned = isApiVersioned;
return (this);
}
/*******************************************************************************
** Getter for apiName
*******************************************************************************/
public String getApiName()
{
return (this.apiName);
}
/*******************************************************************************
** Setter for apiName
*******************************************************************************/
public void setApiName(String apiName)
{
this.apiName = apiName;
}
/*******************************************************************************
** Fluent setter for apiName
*******************************************************************************/
public FilterAndColumnsSetupData withApiName(String apiName)
{
this.apiName = apiName;
return (this);
}
/*******************************************************************************
** Getter for apiPath
*******************************************************************************/
public String getApiPath()
{
return (this.apiPath);
}
/*******************************************************************************
** Setter for apiPath
*******************************************************************************/
public void setApiPath(String apiPath)
{
this.apiPath = apiPath;
}
/*******************************************************************************
** Fluent setter for apiPath
*******************************************************************************/
public FilterAndColumnsSetupData withApiPath(String apiPath)
{
this.apiPath = apiPath;
return (this);
}
/*******************************************************************************
** Getter for apiVersion
*******************************************************************************/
public String getApiVersion()
{
return (this.apiVersion);
}
/*******************************************************************************
** Setter for apiVersion
*******************************************************************************/
public void setApiVersion(String apiVersion)
{
this.apiVersion = apiVersion;
}
/*******************************************************************************
** Fluent setter for apiVersion
*******************************************************************************/
public FilterAndColumnsSetupData withApiVersion(String apiVersion)
{
this.apiVersion = apiVersion;
return (this);
}
/*******************************************************************************
* Getter for omitExposedJoins
* @see #withOmitExposedJoins(List)
*******************************************************************************/
public List<String> getOmitExposedJoins()
{
return (this.omitExposedJoins);
}
/*******************************************************************************
* Setter for omitExposedJoins
* @see #withOmitExposedJoins(List)
*******************************************************************************/
public void setOmitExposedJoins(List<String> omitExposedJoins)
{
this.omitExposedJoins = omitExposedJoins;
}
/*******************************************************************************
* Fluent setter for omitExposedJoins
*
* @param omitExposedJoins
* list of tableNames of exposed joins that shouldn't be available in the filter.
* @return this
*******************************************************************************/
public FilterAndColumnsSetupData withOmitExposedJoins(List<String> omitExposedJoins)
{
this.omitExposedJoins = omitExposedJoins;
return (this);
}
}

View File

@ -66,7 +66,6 @@ public enum WidgetType
// record view/edit widgets //
//////////////////////////////
CHILD_RECORD_LIST("childRecordList"),
CUSTOM_COMPONENT("customComponent"),
DYNAMIC_FORM("dynamicForm"),
DATA_BAG_VIEWER("dataBagViewer"),
PIVOT_TABLE_SETUP("pivotTableSetup"),

View File

@ -49,7 +49,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -209,7 +208,6 @@ public abstract class QRecordEntity
try
{
QRecord qRecord = new QRecord();
qRecord.setTableName(tableName());
for(QRecordEntityField qRecordEntityField : getFieldList(this.getClass()))
{
@ -327,13 +325,13 @@ public abstract class QRecordEntity
List<QRecordEntityField> fieldList = new ArrayList<>();
for(Method possibleGetter : c.getMethods())
{
if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, true))
if(isGetter(possibleGetter))
{
Optional<Method> setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
Optional<Method> setter = getSetterForGetter(c, possibleGetter);
if(setter.isPresent())
{
String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
String fieldName = getFieldNameFromGetter(possibleGetter);
Optional<QField> fieldAnnotation = getQFieldAnnotation(c, fieldName);
if(fieldAnnotation.isPresent())
@ -380,19 +378,19 @@ public abstract class QRecordEntity
List<QRecordEntityAssociation> associationList = new ArrayList<>();
for(Method possibleGetter : c.getMethods())
{
if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, true))
if(isGetter(possibleGetter))
{
Optional<Method> setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
Optional<Method> setter = getSetterForGetter(c, possibleGetter);
if(setter.isPresent())
{
String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
String fieldName = getFieldNameFromGetter(possibleGetter);
Optional<QAssociation> associationAnnotation = getQAssociationAnnotation(c, fieldName);
if(associationAnnotation.isPresent())
{
@SuppressWarnings("unchecked")
Class<? extends QRecordEntity> listTypeParam = (Class<? extends QRecordEntity>) ReflectiveBeanLikeClassUtils.getListTypeParam(possibleGetter.getReturnType(), possibleGetter.getAnnotatedReturnType());
Class<? extends QRecordEntity> listTypeParam = (Class<? extends QRecordEntity>) getListTypeParam(possibleGetter.getReturnType(), possibleGetter.getAnnotatedReturnType());
associationList.add(new QRecordEntityAssociation(fieldName, possibleGetter, setter.get(), listTypeParam, associationAnnotation.orElse(null)));
}
}

View File

@ -28,7 +28,6 @@ import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Map;
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -171,11 +170,6 @@ public class QRecordEntityField
{
return (ValueUtils.getValueAsByteArray(value));
}
if(type.equals(Map.class))
{
return (ValueUtils.getValueAsMap(value));
}
}
catch(Exception e)
{

View File

@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.model.data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -258,39 +257,4 @@ public class QRecordWithJoinedRecords extends QRecord
return (this);
}
/***************************************************************************
* Given an object of this type (`this`), add a list of join-records to it,
* producing a new list, which is `this` × joinRecordList.
* One may want to use this in a loop to build a larger cross product - more
* to come here in that spirit.
* @param joinTable name of the join table (e.g., to prefix the join fields)
* @param joinRecordList list of join records. may not be null. may be 0+ size.
* @return list of new QRecordWithJoinedRecords, based on `this` with each
* joinRecord added (e.g., output list is same size as joinRecordList).
* Note that does imply an 'inner' style join - where - if the joinRecordList
* is empty, you'll get back an empty list!
***************************************************************************/
public List<QRecordWithJoinedRecords> buildCrossProduct(String joinTable, List<QRecord> joinRecordList)
{
List<QRecordWithJoinedRecords> rs = new ArrayList<>();
for(QRecord joinRecord : joinRecordList)
{
/////////////////////////////////////////////////////////////
// essentially clone the existing QRecordWithJoinedRecords //
/////////////////////////////////////////////////////////////
QRecordWithJoinedRecords newRecord = new QRecordWithJoinedRecords(mainRecord);
components.forEach((k, v) -> newRecord.addJoinedRecordValues(k, v));
///////////////////////////////////////////
// now add the new join record to it too //
///////////////////////////////////////////
newRecord.addJoinedRecordValues(joinTable, joinRecord);
rs.add(newRecord);
}
return (rs);
}
}

View File

@ -177,18 +177,6 @@ public class MetaDataProducerHelper
/////////////////////////////////////////////////////////////////////////////////////////////
// sort them by sort order, then by the type that they return, as set up in the static map //
/////////////////////////////////////////////////////////////////////////////////////////////
sortMetaDataProducers(producers);
return (producers);
}
/***************************************************************************
**
***************************************************************************/
public static void sortMetaDataProducers(List<MetaDataProducerInterface<?>> producers)
{
producers.sort(Comparator
.comparing((MetaDataProducerInterface<?> p) -> p.getSortOrder())
.thenComparing((MetaDataProducerInterface<?> p) ->
@ -203,8 +191,9 @@ public class MetaDataProducerHelper
return (0);
}
}));
}
return (producers);
}
/*******************************************************************************
@ -428,7 +417,7 @@ public class MetaDataProducerHelper
return (null);
}
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName, childTable.childJoin().orderBy(), childTable.childJoin().isOneToOne());
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName, childTable.childJoin().orderBy());
producer.setSourceClass(entityClass);
return producer;
}

View File

@ -86,7 +86,7 @@ public class MetaDataProducerMultiOutput implements MetaDataProducerOutput, Sour
{
List<T> rs = new ArrayList<>();
for(MetaDataProducerOutput content : CollectionUtils.nonNullList(contents))
for(MetaDataProducerOutput content : contents)
{
if(content instanceof MetaDataProducerMultiOutput multiOutput)
{
@ -145,36 +145,4 @@ public class MetaDataProducerMultiOutput implements MetaDataProducerOutput, Sour
setSourceQBitName(sourceQBitName);
return this;
}
/***************************************************************************
* get a typed and named meta-data object out of this output container.
*
* @param <C> the type of the object to return, e.g., QTableMetaData
* @param outputClass the class for the type to return
* @param name the name of the object, e.g., a table or process name.
* @return the requested TopLevelMetaDataInterface object (in the requested
* type), or null if not found.
***************************************************************************/
public <C extends TopLevelMetaDataInterface> C get(Class<C> outputClass, String name)
{
for(MetaDataProducerOutput content : CollectionUtils.nonNullList(contents))
{
if(content instanceof MetaDataProducerMultiOutput multiOutput)
{
C c = multiOutput.get(outputClass, name);
if(c != null)
{
return (c);
}
}
else if(outputClass.isInstance(content) && name.equals(((TopLevelMetaDataInterface)content).getName()))
{
return (C) content;
}
}
return null;
}
}

View File

@ -28,7 +28,6 @@ package com.kingsrook.qqq.backend.core.model.metadata;
*******************************************************************************/
public enum QAuthenticationType
{
OAUTH2("OAuth2"),
AUTH_0("auth0"),
TABLE_BASED("tableBased"),
FULLY_ANONYMOUS("fullyAnonymous"),

View File

@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.model.metadata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
@ -31,7 +30,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@ -67,7 +65,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.scheduler.schedulable.SchedulableType;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import io.github.cdimascio.dotenv.Dotenv;
import io.github.cdimascio.dotenv.DotenvEntry;
@ -119,8 +116,6 @@ public class QInstance
private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance();
private QAuditRules defaultAuditRules = QAuditRules.defaultInstanceLevelNone();
private ListingHash<String, QCodeReference> tableCustomizers;
@Deprecated(since = "migrated to metaDataCustomizer")
private QCodeReference metaDataFilter = null;
@ -1255,7 +1250,7 @@ public class QInstance
{
this.supplementalMetaData = new HashMap<>();
}
this.supplementalMetaData.put(supplementalMetaData.getName(), supplementalMetaData);
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
return (this);
}
@ -1628,76 +1623,4 @@ public class QInstance
return (this);
}
/*******************************************************************************
** Getter for tableCustomizers
*******************************************************************************/
public ListingHash<String, QCodeReference> getTableCustomizers()
{
return (this.tableCustomizers);
}
/*******************************************************************************
** Setter for tableCustomizers
*******************************************************************************/
public void setTableCustomizers(ListingHash<String, QCodeReference> tableCustomizers)
{
this.tableCustomizers = tableCustomizers;
}
/*******************************************************************************
** Fluent setter for tableCustomizers
*******************************************************************************/
public QInstance withTableCustomizers(ListingHash<String, QCodeReference> tableCustomizers)
{
this.tableCustomizers = tableCustomizers;
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public QInstance withTableCustomizer(String role, QCodeReference customizer)
{
if(this.tableCustomizers == null)
{
this.tableCustomizers = new ListingHash<>();
}
this.tableCustomizers.add(role, customizer);
return (this);
}
/*******************************************************************************
**
*******************************************************************************/
public QInstance withTableCustomizer(TableCustomizers tableCustomizer, QCodeReference customizer)
{
return (withTableCustomizer(tableCustomizer.getRole(), customizer));
}
/*******************************************************************************
** Getter for tableCustomizers
*******************************************************************************/
public List<QCodeReference> getTableCustomizers(TableCustomizers tableCustomizer)
{
if(this.tableCustomizers == null)
{
return (Collections.emptyList());
}
return (this.tableCustomizers.getOrDefault(tableCustomizer.getRole(), Collections.emptyList()));
}
}

View File

@ -1,34 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.model.metadata;
import java.io.Serializable;
/*******************************************************************************
** interface common among all objects that can be considered qqq meta data -
** e.g., stored in a QInstance.
*******************************************************************************/
public interface QMetaDataObject extends Serializable
{
}

View File

@ -22,21 +22,28 @@
package com.kingsrook.qqq.backend.core.model.metadata;
import java.util.function.Supplier;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Base-class for instance-level meta-data defined by some supplemental module, etc,
** outside of qqq core
*******************************************************************************/
public interface QSupplementalInstanceMetaData extends TopLevelMetaDataInterface
public abstract class QSupplementalInstanceMetaData implements TopLevelMetaDataInterface
{
/*******************************************************************************
** Getter for type
*******************************************************************************/
public abstract String getType();
/*******************************************************************************
**
*******************************************************************************/
default void enrich(QInstance qInstance)
public void enrich(QTableMetaData table)
{
////////////////////////
// noop in base class //
@ -48,7 +55,7 @@ public interface QSupplementalInstanceMetaData extends TopLevelMetaDataInterface
/*******************************************************************************
**
*******************************************************************************/
default void validate(QInstance qInstance, QInstanceValidator validator)
public void validate(QInstance qInstance, QInstanceValidator validator)
{
////////////////////////
// noop in base class //
@ -61,33 +68,9 @@ public interface QSupplementalInstanceMetaData extends TopLevelMetaDataInterface
**
*******************************************************************************/
@Override
default void addSelfToInstance(QInstance qInstance)
public void addSelfToInstance(QInstance qInstance)
{
qInstance.withSupplementalMetaData(this);
}
/***************************************************************************
**
***************************************************************************/
static <S extends QSupplementalInstanceMetaData> S of(QInstance qInstance, String name)
{
return ((S) qInstance.getSupplementalMetaData(name));
}
/***************************************************************************
**
***************************************************************************/
static <S extends QSupplementalInstanceMetaData> S ofOrWithNew(QInstance qInstance, String name, Supplier<S> supplier)
{
S s = (S) qInstance.getSupplementalMetaData(name);
if(s == null)
{
s = supplier.get();
s.addSelfToInstance(qInstance);
}
return (s);
}
}

View File

@ -26,7 +26,7 @@ package com.kingsrook.qqq.backend.core.model.metadata;
** Interface for meta-data classes that can be added directly (e.g, at the top
** level) to a QInstance (such as a QTableMetaData - not a QFieldMetaData).
*******************************************************************************/
public interface TopLevelMetaDataInterface extends MetaDataProducerOutput, QMetaDataObject
public interface TopLevelMetaDataInterface extends MetaDataProducerOutput
{
/*******************************************************************************

View File

@ -22,13 +22,10 @@
package com.kingsrook.qqq.backend.core.model.metadata.audits;
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
/*******************************************************************************
**
*******************************************************************************/
public class QAuditRules implements QMetaDataObject
public class QAuditRules
{
private AuditLevel auditLevel;

View File

@ -1,320 +0,0 @@
/*
* 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.backend.core.model.metadata.authentication;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.OAuth2AuthenticationModule;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** Meta-data to provide details of an OAuth2 Authentication module
*******************************************************************************/
public class OAuth2AuthenticationMetaData extends QAuthenticationMetaData
{
private String baseUrl;
private String tokenUrl;
private String clientId;
private String scopes;
private String userSessionTableName;
private String redirectStateTableName;
////////////////////////////////////////////////////////////////////////////////////////
// keep this secret, on the server - don't let it be serialized and sent to a client! //
////////////////////////////////////////////////////////////////////////////////////////
@JsonIgnore
private String clientSecret;
/*******************************************************************************
** Default Constructor.
*******************************************************************************/
public OAuth2AuthenticationMetaData()
{
super();
setType(QAuthenticationType.OAUTH2);
//////////////////////////////////////////////////////////
// ensure this module is registered with the dispatcher //
//////////////////////////////////////////////////////////
QAuthenticationModuleDispatcher.registerModule(QAuthenticationType.OAUTH2.getName(), OAuth2AuthenticationModule.class.getName());
}
/***************************************************************************
**
***************************************************************************/
@Override
public void validate(QInstance qInstance, QInstanceValidator qInstanceValidator)
{
super.validate(qInstance, qInstanceValidator);
String prefix = "OAuth2AuthenticationMetaData (named '" + getName() + "'): ";
qInstanceValidator.assertCondition(StringUtils.hasContent(baseUrl), prefix + "baseUrl must be set");
qInstanceValidator.assertCondition(StringUtils.hasContent(clientId), prefix + "clientId must be set");
qInstanceValidator.assertCondition(StringUtils.hasContent(clientSecret), prefix + "clientSecret must be set");
qInstanceValidator.assertCondition(StringUtils.hasContent(scopes), prefix + "scopes must be set");
if(qInstanceValidator.assertCondition(StringUtils.hasContent(userSessionTableName), prefix + "userSessionTableName must be set"))
{
qInstanceValidator.assertCondition(qInstance.getTable(userSessionTableName) != null, prefix + "userSessionTableName ('" + userSessionTableName + "') was not found in the instance");
}
if(qInstanceValidator.assertCondition(StringUtils.hasContent(redirectStateTableName), prefix + "redirectStateTableName must be set"))
{
qInstanceValidator.assertCondition(qInstance.getTable(redirectStateTableName) != null, prefix + "redirectStateTableName ('" + redirectStateTableName + "') was not found in the instance");
}
}
/*******************************************************************************
** Fluent setter, override to help fluent flows
*******************************************************************************/
public OAuth2AuthenticationMetaData withBaseUrl(String baseUrl)
{
setBaseUrl(baseUrl);
return this;
}
/*******************************************************************************
** Getter for baseUrl
**
*******************************************************************************/
public String getBaseUrl()
{
return baseUrl;
}
/*******************************************************************************
** Setter for baseUrl
**
*******************************************************************************/
public void setBaseUrl(String baseUrl)
{
this.baseUrl = baseUrl;
}
/*******************************************************************************
** Fluent setter, override to help fluent flows
*******************************************************************************/
public OAuth2AuthenticationMetaData withClientId(String clientId)
{
setClientId(clientId);
return this;
}
/*******************************************************************************
** Getter for clientId
**
*******************************************************************************/
public String getClientId()
{
return clientId;
}
/*******************************************************************************
** Setter for clientId
**
*******************************************************************************/
public void setClientId(String clientId)
{
this.clientId = clientId;
}
/*******************************************************************************
** Fluent setter, override to help fluent flows
*******************************************************************************/
public OAuth2AuthenticationMetaData withClientSecret(String clientSecret)
{
setClientSecret(clientSecret);
return this;
}
/*******************************************************************************
** Getter for clientSecret
**
*******************************************************************************/
public String getClientSecret()
{
return clientSecret;
}
/*******************************************************************************
** Setter for clientSecret
**
*******************************************************************************/
public void setClientSecret(String clientSecret)
{
this.clientSecret = clientSecret;
}
/*******************************************************************************
** Getter for tokenUrl
*******************************************************************************/
public String getTokenUrl()
{
return (this.tokenUrl);
}
/*******************************************************************************
** Setter for tokenUrl
*******************************************************************************/
public void setTokenUrl(String tokenUrl)
{
this.tokenUrl = tokenUrl;
}
/*******************************************************************************
** Fluent setter for tokenUrl
*******************************************************************************/
public OAuth2AuthenticationMetaData withTokenUrl(String tokenUrl)
{
this.tokenUrl = tokenUrl;
return (this);
}
/*******************************************************************************
** Getter for userSessionTableName
*******************************************************************************/
public String getUserSessionTableName()
{
return (this.userSessionTableName);
}
/*******************************************************************************
** Setter for userSessionTableName
*******************************************************************************/
public void setUserSessionTableName(String userSessionTableName)
{
this.userSessionTableName = userSessionTableName;
}
/*******************************************************************************
** Fluent setter for userSessionTableName
*******************************************************************************/
public OAuth2AuthenticationMetaData withUserSessionTableName(String userSessionTableName)
{
this.userSessionTableName = userSessionTableName;
return (this);
}
/*******************************************************************************
** Getter for redirectStateTableName
*******************************************************************************/
public String getRedirectStateTableName()
{
return (this.redirectStateTableName);
}
/*******************************************************************************
** Setter for redirectStateTableName
*******************************************************************************/
public void setRedirectStateTableName(String redirectStateTableName)
{
this.redirectStateTableName = redirectStateTableName;
}
/*******************************************************************************
** Fluent setter for redirectStateTableName
*******************************************************************************/
public OAuth2AuthenticationMetaData withRedirectStateTableName(String redirectStateTableName)
{
this.redirectStateTableName = redirectStateTableName;
return (this);
}
/*******************************************************************************
** Getter for scopes
*******************************************************************************/
public String getScopes()
{
return (this.scopes);
}
/*******************************************************************************
** Setter for scopes
*******************************************************************************/
public void setScopes(String scopes)
{
this.scopes = scopes;
}
/*******************************************************************************
** Fluent setter for scopes
*******************************************************************************/
public OAuth2AuthenticationMetaData withScopes(String scopes)
{
this.scopes = scopes;
return (this);
}
}

View File

@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.model.metadata.authentication;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFilter;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
@ -226,15 +225,4 @@ public class QAuthenticationMetaData implements TopLevelMetaDataInterface
return (this);
}
/***************************************************************************
**
***************************************************************************/
public void validate(QInstance qInstance, QInstanceValidator qInstanceValidator)
{
//////////////////
// noop at base //
//////////////////
}
}

View File

@ -23,14 +23,13 @@ package com.kingsrook.qqq.backend.core.model.metadata.code;
import java.io.Serializable;
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
/*******************************************************************************
** 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, Cloneable, QMetaDataObject
public class QCodeReference implements Serializable, Cloneable
{
private String name;
private QCodeType codeType;

View File

@ -40,13 +40,11 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole;
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -56,7 +54,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
** Meta-data to represent a single field in a table.
**
*******************************************************************************/
public class QFieldMetaData implements Cloneable, QMetaDataObject
public class QFieldMetaData implements Cloneable
{
private static final QLogger LOG = QLogger.getLogger(QFieldMetaData.class);
@ -189,7 +187,7 @@ public class QFieldMetaData implements Cloneable, QMetaDataObject
{
try
{
this.name = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(getter);
this.name = QRecordEntity.getFieldNameFromGetter(getter);
this.type = QFieldType.fromClass(getter.getReturnType());
@SuppressWarnings("unchecked")

View File

@ -22,10 +22,6 @@
package com.kingsrook.qqq.backend.core.model.metadata.fields;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Base-class for field-level meta-data defined by some supplemental module, etc,
** outside of qqq core
@ -33,43 +29,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
public abstract class QSupplementalFieldMetaData
{
/*******************************************************************************
**
*******************************************************************************/
public boolean includeInFrontendMetaData()
{
return (false);
}
/*******************************************************************************
** Getter for type
*******************************************************************************/
public abstract String getType();
/***************************************************************************
**
***************************************************************************/
public void enrich(QInstance qInstance, QFieldMetaData fieldMetaData)
{
////////////////////////
// noop in base class //
////////////////////////
}
/*******************************************************************************
**
*******************************************************************************/
public void validate(QInstance qInstance, QFieldMetaData fieldMetaData, QInstanceValidator qInstanceValidator)
{
////////////////////////
// noop in base class //
////////////////////////
}
}

View File

@ -1,174 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. 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.model.metadata.fields;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
** Field behavior that changes the whitespace of string values.
*******************************************************************************/
public enum WhiteSpaceBehavior implements FieldBehavior<WhiteSpaceBehavior>, FieldBehaviorForFrontend, FieldFilterBehavior<WhiteSpaceBehavior>
{
NONE(null),
REMOVE_ALL_WHITESPACE((String s) -> s.chars().filter(c -> !Character.isWhitespace(c)).collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()),
TRIM((String s) -> s.trim()),
TRIM_LEFT((String s) -> s.stripLeading()),
TRIM_RIGHT((String s) -> s.stripTrailing());
private final Function<String, String> function;
/*******************************************************************************
**
*******************************************************************************/
WhiteSpaceBehavior(Function<String, String> function)
{
this.function = function;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public WhiteSpaceBehavior getDefault()
{
return (NONE);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
{
if(this.equals(NONE))
{
return;
}
switch(this)
{
case REMOVE_ALL_WHITESPACE, TRIM, TRIM_LEFT, TRIM_RIGHT -> applyFunction(recordList, table, field);
default -> throw new IllegalStateException("Unexpected enum value: " + this);
}
}
/*******************************************************************************
**
*******************************************************************************/
private void applyFunction(List<QRecord> recordList, QTableMetaData table, QFieldMetaData field)
{
String fieldName = field.getName();
for(QRecord record : CollectionUtils.nonNullList(recordList))
{
String value = record.getValueString(fieldName);
if(value != null && function != null)
{
record.setValue(fieldName, function.apply(value));
}
}
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public Serializable applyToFilterCriteriaValue(Serializable value, QInstance instance, QTableMetaData table, QFieldMetaData field)
{
if(this.equals(NONE) || function == null)
{
return (value);
}
if(value instanceof String s)
{
String newValue = function.apply(s);
if(!Objects.equals(value, newValue))
{
return (newValue);
}
}
return (value);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public boolean allowMultipleBehaviorsOfThisType()
{
return (false);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public List<String> validateBehaviorConfiguration(QTableMetaData tableMetaData, QFieldMetaData fieldMetaData)
{
if(this == NONE)
{
return Collections.emptyList();
}
List<String> errors = new ArrayList<>();
String errorSuffix = " field [" + fieldMetaData.getName() + "] in table [" + tableMetaData.getName() + "]";
if(fieldMetaData.getType() != null)
{
if(!fieldMetaData.getType().isStringLike())
{
errors.add("A WhiteSpaceBehavior was a applied to a non-String-like field:" + errorSuffix);
}
}
return (errors);
}
}

View File

@ -24,18 +24,14 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehaviorForFrontend;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QSupplementalFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -55,7 +51,6 @@ public class QFrontendFieldMetaData implements Serializable
private boolean isRequired;
private boolean isEditable;
private boolean isHeavy;
private boolean isHidden;
private Integer gridColumns;
private String possibleValueSourceName;
private String displayFormat;
@ -65,10 +60,8 @@ public class QFrontendFieldMetaData implements Serializable
private List<FieldAdornment> adornments;
private List<QHelpContent> helpContents;
private QPossibleValueSource inlinePossibleValueSource;
private QQueryFilter possibleValueSourceFilter;
private List<FieldBehaviorForFrontend> behaviors;
private Map<String, QSupplementalFieldMetaData> supplementalFieldMetaData;
private List<FieldBehaviorForFrontend> behaviors;
//////////////////////////////////////////////////////////////////////////////////
// do not add setters. take values from the source-object in the constructor!! //
@ -86,7 +79,6 @@ public class QFrontendFieldMetaData implements Serializable
this.isRequired = fieldMetaData.getIsRequired();
this.isEditable = fieldMetaData.getIsEditable();
this.isHeavy = fieldMetaData.getIsHeavy();
this.isHidden = fieldMetaData.getIsHidden();
this.gridColumns = fieldMetaData.getGridColumns();
this.possibleValueSourceName = fieldMetaData.getPossibleValueSourceName();
this.displayFormat = fieldMetaData.getDisplayFormat();
@ -95,7 +87,6 @@ public class QFrontendFieldMetaData implements Serializable
this.helpContents = fieldMetaData.getHelpContents();
this.inlinePossibleValueSource = fieldMetaData.getInlinePossibleValueSource();
this.maxLength = fieldMetaData.getMaxLength();
this.possibleValueSourceFilter = fieldMetaData.getPossibleValueSourceFilter();
for(FieldBehavior<?> behavior : CollectionUtils.nonNullCollection(fieldMetaData.getBehaviors()))
{
@ -108,19 +99,6 @@ public class QFrontendFieldMetaData implements Serializable
behaviors.add(fbff);
}
}
for(Map.Entry<String, QSupplementalFieldMetaData> entry : CollectionUtils.nonNullMap(fieldMetaData.getSupplementalMetaData()).entrySet())
{
QSupplementalFieldMetaData supplementalFieldMetaData = entry.getValue();
if(supplementalFieldMetaData.includeInFrontendMetaData())
{
if(this.supplementalFieldMetaData == null)
{
this.supplementalFieldMetaData = new HashMap<>();
}
this.supplementalFieldMetaData.put(entry.getKey(), supplementalFieldMetaData);
}
}
}
@ -191,17 +169,6 @@ public class QFrontendFieldMetaData implements Serializable
/*******************************************************************************
** Getter for isHidden
**
*******************************************************************************/
public boolean getIsHidden()
{
return isHidden;
}
/*******************************************************************************
** Getter for gridColumns
**
@ -287,37 +254,4 @@ public class QFrontendFieldMetaData implements Serializable
{
return behaviors;
}
/*******************************************************************************
** Getter for supplementalFieldMetaData
**
*******************************************************************************/
public Map<String, QSupplementalFieldMetaData> getSupplementalFieldMetaData()
{
return supplementalFieldMetaData;
}
/*******************************************************************************
** Getter for maxLength
**
*******************************************************************************/
public Integer getMaxLength()
{
return maxLength;
}
/*******************************************************************************
** Getter for possibleValueSourceFilter
**
*******************************************************************************/
public QQueryFilter getPossibleValueSourceFilter()
{
return possibleValueSourceFilter;
}
}

View File

@ -85,35 +85,23 @@ public class QFrontendTableMetaData
// do not add setters. take values from the source-object in the constructor!! //
//////////////////////////////////////////////////////////////////////////////////
/***************************************************************************
** standard constructor - uses all fields on the table.
***************************************************************************/
public QFrontendTableMetaData(AbstractActionInput actionInput, QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFullMetaData, boolean includeJoins)
{
this(actionInput, backendForTable, tableMetaData, includeFullMetaData, includeJoins, tableMetaData.getFields());
}
/*******************************************************************************
** alternative constructor - takes a map of fields to use (e.g., for an old
** api version of the table w/ different fields!)
**
*******************************************************************************/
public QFrontendTableMetaData(AbstractActionInput actionInput, QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFullMetaData, boolean includeJoins, Map<String, QFieldMetaData> overrideFields)
public QFrontendTableMetaData(AbstractActionInput actionInput, QBackendMetaData backendForTable, QTableMetaData tableMetaData, boolean includeFullMetaData, boolean includeJoins)
{
this.name = tableMetaData.getName();
this.label = tableMetaData.getLabel();
this.isHidden = tableMetaData.getIsHidden();
Map<String, QFieldMetaData> inputFields = overrideFields == null ? tableMetaData.getFields() : overrideFields;
if(includeFullMetaData)
{
this.primaryKeyField = tableMetaData.getPrimaryKeyField();
this.fields = new HashMap<>();
for(String fieldName : inputFields.keySet())
for(String fieldName : tableMetaData.getFields().keySet())
{
QFieldMetaData field = inputFields.get(fieldName);
QFieldMetaData field = tableMetaData.getField(fieldName);
if(!field.getIsHidden())
{
this.fields.put(fieldName, new QFrontendFieldMetaData(field));

Some files were not shown because too many files have changed in this diff Show More