mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-20 22:18:43 +00:00
Compare commits
115 Commits
snapshot-f
...
version-0.
Author | SHA1 | Date | |
---|---|---|---|
31edb6a7fe | |||
338670118d | |||
32573bdf78 | |||
bd683253a5 | |||
75279c2e6c | |||
9e33ac564d | |||
7fe7c2d0a0 | |||
79b9f0e921 | |||
a75ec9a0c5 | |||
b11f1fb394 | |||
3c927693f1 | |||
cb41f239b8 | |||
8648c67a98 | |||
d11ae90ad6 | |||
8395dfaa52 | |||
3273e56b17 | |||
9b1786dc01 | |||
429513f337 | |||
6ee8dad45f | |||
f380d44dd2 | |||
2cf14e543c | |||
1669741d19 | |||
2be41d8714 | |||
3e7e416a2a | |||
b377af846a | |||
6b5b971368 | |||
f6e09f1d57 | |||
f069358764 | |||
c509b6da38 | |||
e788929d67 | |||
18c94943cb | |||
b24a990043 | |||
a4295df20d | |||
3398b812ce | |||
9e9f266878 | |||
7cbd6705e1 | |||
1eb078d916 | |||
82201286d4 | |||
dc84a9ef55 | |||
b2cf1cc83b | |||
848353d804 | |||
e8978a7f92 | |||
9d24e61949 | |||
c748977a1b | |||
fcae58168e | |||
564a5e1095 | |||
3d7df30db4 | |||
a63d5d0c65 | |||
e923ce1013 | |||
231ca165a1 | |||
35c1150f80 | |||
37f89110e7 | |||
4766d2440d | |||
ad07f3e1f8 | |||
7ab2f332e9 | |||
00a3f6632c | |||
9afbe7c3a9 | |||
27682870a1 | |||
98031b53cb | |||
e1fd6d51c4 | |||
0fd2430866 | |||
9e9d1960c8 | |||
64380dd849 | |||
c21e9131a0 | |||
ec04986434 | |||
f7d217a126 | |||
f9cca885ed | |||
64f706a98e | |||
7ff7ae3a0c | |||
d7e295881f | |||
9fd55746ca | |||
121f9aa477 | |||
c8edd14833 | |||
f7b6028ba1 | |||
bf2836b69f | |||
d3f3e25ed5 | |||
d0839dc93c | |||
5540f85466 | |||
1a66f45425 | |||
90ac1bb9c3 | |||
faafacc722 | |||
4508dea767 | |||
7c6c02ab28 | |||
e1a63752ca | |||
059b746597 | |||
11e1fb86b2 | |||
eb8bf12047 | |||
66b2b4ff4c | |||
a6e0741175 | |||
2b7432167d | |||
0a12c76829 | |||
3fe6828550 | |||
27a17183ae | |||
94bf10fe6e | |||
610915bf94 | |||
3e26ea94ee | |||
e6190b4fe2 | |||
1c582621aa | |||
b91da93858 | |||
522dafca69 | |||
82f0f177fb | |||
9c79ce3272 | |||
85eae36c28 | |||
485bc618e0 | |||
6f6f9af17d | |||
65fe6a002c | |||
ede497ee85 | |||
5a56b5d9b4 | |||
e10a1e40da | |||
8816bc89c3 | |||
759972b70c | |||
425629de52 | |||
71ecde74df | |||
7e34b97998 | |||
1062f00ed4 |
10
.github/actions/install_asciidoctor/action.yml
vendored
Normal file
10
.github/actions/install_asciidoctor/action.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
name: install_asciidoctor
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- name: Install asciidoctor
|
||||
run: |-
|
||||
sudo apt-get update
|
||||
sudo apt install -y asciidoctor
|
||||
shell: bash
|
16
.github/actions/install_java17/action.yml
vendored
Normal file
16
.github/actions/install_java17/action.yml
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
name: install_java17
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install Java 17
|
||||
run: |-
|
||||
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
|
||||
shell: bash
|
||||
- name: Install html2text
|
||||
run: |-
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y html2text
|
||||
shell: bash
|
22
.github/actions/mvn_jar_deploy/action.yml
vendored
Normal file
22
.github/actions/mvn_jar_deploy/action.yml
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
name: mvn_jar_deploy
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- name: Adjust pom version
|
||||
run: ".circleci/adjust-pom-version.sh"
|
||||
shell: bash
|
||||
- name: restore_cache
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
key: v1-dependencies-{{ checksum "pom.xml" }}
|
||||
path: UPDATE_ME
|
||||
restore-keys: v1-dependencies-{{ checksum "pom.xml" }}
|
||||
- name: Run Maven Jar Deploy
|
||||
run: mvn -s .circleci/mvn-settings.xml -T4 flatten:flatten jar:jar deploy:deploy
|
||||
shell: bash
|
||||
- name: save_cache
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
path: "~/.m2"
|
||||
key: v1-dependencies-{{ checksum "pom.xml" }}
|
61
.github/actions/mvn_verify/action.yml
vendored
Normal file
61
.github/actions/mvn_verify/action.yml
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
name: mvn_verify
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- name: restore_cache
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
key: v1-dependencies-{{ checksum "pom.xml" }}
|
||||
path: UPDATE_ME
|
||||
restore-keys: v1-dependencies-{{ checksum "pom.xml" }}
|
||||
- name: Write .env
|
||||
run: echo "RDBMS_PASSWORD=$RDBMS_PASSWORD" >> qqq-sample-project/.env
|
||||
shell: bash
|
||||
- name: Run Maven Verify
|
||||
run: mvn -s .circleci/mvn-settings.xml -T4 verify
|
||||
shell: bash
|
||||
- uses: "./.github/actions/store_jacoco_site"
|
||||
with:
|
||||
module: qqq-backend-core
|
||||
- uses: "./.github/actions/store_jacoco_site"
|
||||
with:
|
||||
module: qqq-backend-module-filesystem
|
||||
- uses: "./.github/actions/store_jacoco_site"
|
||||
with:
|
||||
module: qqq-backend-module-rdbms
|
||||
- uses: "./.github/actions/store_jacoco_site"
|
||||
with:
|
||||
module: qqq-backend-module-api
|
||||
- uses: "./.github/actions/store_jacoco_site"
|
||||
with:
|
||||
module: qqq-middleware-api
|
||||
- uses: "./.github/actions/store_jacoco_site"
|
||||
with:
|
||||
module: qqq-middleware-javalin
|
||||
- uses: "./.github/actions/store_jacoco_site"
|
||||
with:
|
||||
module: qqq-middleware-picocli
|
||||
- uses: "./.github/actions/store_jacoco_site"
|
||||
with:
|
||||
module: qqq-middleware-slack
|
||||
- uses: "./.github/actions/store_jacoco_site"
|
||||
with:
|
||||
module: qqq-language-support-javascript
|
||||
- uses: "./.github/actions/store_jacoco_site"
|
||||
with:
|
||||
module: qqq-sample-project
|
||||
- name: Save test results
|
||||
run: |-
|
||||
mkdir -p ~/test-results/junit/
|
||||
find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} ~/test-results/junit/ \;
|
||||
if: always()
|
||||
shell: bash
|
||||
- uses: actions/upload-artifact@v4.1.0
|
||||
with:
|
||||
path: "~/test-results"
|
||||
- name: save_cache
|
||||
uses: actions/cache@v3.3.2
|
||||
with:
|
||||
path: "~/.m2"
|
||||
key: v1-dependencies-{{ checksum "pom.xml" }}
|
9
.github/actions/run_asciidoctor/action.yml
vendored
Normal file
9
.github/actions/run_asciidoctor/action.yml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
name: run_asciidoctor
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Run asciidoctor
|
||||
run: |-
|
||||
cd docs
|
||||
asciidoctor -a docinfo=shared index.adoc
|
||||
shell: bash
|
13
.github/actions/store_jacoco_site/action.yml
vendored
Normal file
13
.github/actions/store_jacoco_site/action.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
name: store_jacoco_site
|
||||
inputs:
|
||||
module:
|
||||
required: false
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/upload-artifact@v4.1.0
|
||||
with:
|
||||
path: "${{ inputs.module }}/target/site/jacoco/index.html"
|
||||
- uses: actions/upload-artifact@v4.1.0
|
||||
with:
|
||||
path: "${{ inputs.module }}/target/site/jacoco/jacoco-resources"
|
9
.github/actions/upload_docs_site/action.yml
vendored
Normal file
9
.github/actions/upload_docs_site/action.yml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
name: upload_docs_site
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: scp html to justinsgotskinnylegs.com
|
||||
run: |-
|
||||
cd docs
|
||||
scp index.html dkelkhoff@45.79.44.221:/mnt/first-volume/dkelkhoff/nginx/html/justinsgotskinnylegs.com/qqq-docs.html
|
||||
shell: bash
|
61
.github/workflows/codacy.yml
vendored
Normal file
61
.github/workflows/codacy.yml
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
# This workflow checks out code, performs a Codacy security scan
|
||||
# and integrates the results with the
|
||||
# GitHub Advanced Security code scanning feature. For more information on
|
||||
# the Codacy security scan action usage and parameters, see
|
||||
# https://github.com/codacy/codacy-analysis-cli-action.
|
||||
# For more information on Codacy Analysis CLI in general, see
|
||||
# https://github.com/codacy/codacy-analysis-cli.
|
||||
|
||||
name: Codacy Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "security" ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "security" ]
|
||||
schedule:
|
||||
- cron: '26 5 * * 4'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
codacy-security-scan:
|
||||
permissions:
|
||||
contents: read # for actions/checkout to fetch code
|
||||
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
name: Codacy Security Scan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# Checkout the repository to the GitHub Actions runner
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis
|
||||
- name: Run Codacy Analysis CLI
|
||||
uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b
|
||||
with:
|
||||
# Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository
|
||||
# You can also omit the token and run the tools that support default configurations
|
||||
project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}
|
||||
verbose: true
|
||||
output: results.sarif
|
||||
format: sarif
|
||||
# Adjust severity of non-security issues
|
||||
gh-code-scanning-compat: true
|
||||
# Force 0 exit code to allow SARIF file generation
|
||||
# This will handover control about PR rejection to the GitHub side
|
||||
max-allowed-issues: 2147483647
|
||||
|
||||
# Upload the SARIF file generated in the previous step
|
||||
- name: Upload SARIF results file
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: results.sarif
|
93
.github/workflows/codeql.yml
vendored
Normal file
93
.github/workflows/codeql.yml
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "security" ]
|
||||
pull_request:
|
||||
branches: [ "security" ]
|
||||
schedule:
|
||||
- cron: '31 10 * * 3'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# required to fetch internal or private CodeQL packs
|
||||
packages: read
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: java-kotlin
|
||||
build-mode: none # This mode only analyzes Java. Set this to 'autobuild' or 'manual' to analyze Kotlin too.
|
||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
shell: bash
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
20
.github/workflows/deploy.yml
vendored
Normal file
20
.github/workflows/deploy.yml
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
name: Kingsrook/qqq/deploy
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
jobs:
|
||||
mvn_deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/install_java17"
|
||||
- uses: "./.github/actions/mvn_verify"
|
||||
- uses: "./.github/actions/mvn_jar_deploy"
|
||||
publish_asciidoc:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/install_asciidoctor"
|
||||
- uses: "./.github/actions/run_asciidoctor"
|
||||
- uses: "./.github/actions/upload_docs_site"
|
12
.github/workflows/test_only.yml
vendored
Normal file
12
.github/workflows/test_only.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
name: Kingsrook/qqq/test_only
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
jobs:
|
||||
mvn_test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.0
|
||||
- uses: "./.github/actions/install_java17"
|
||||
- uses: "./.github/actions/mvn_verify"
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -35,3 +35,5 @@ hs_err_pid*
|
||||
*.swp
|
||||
.flattened-pom.xml
|
||||
dependency-reduced-pom.xml
|
||||
/.env.local
|
||||
/.cache/
|
||||
|
@ -19,7 +19,7 @@ You can also use fine-grained jars:
|
||||
|
||||
## License
|
||||
QQQ - Low-code Application Framework for Engineers. \
|
||||
Copyright (C) 2022. Kingsrook, LLC \
|
||||
Copyright (C) 2020-2024. Kingsrook, LLC \
|
||||
651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States \
|
||||
contact@kingsrook.com | https://github.com/Kingsrook/
|
||||
|
||||
|
21
SECURITY.md
Normal file
21
SECURITY.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 5.1.x | :white_check_mark: |
|
||||
| 5.0.x | :x: |
|
||||
| 4.0.x | :white_check_mark: |
|
||||
| < 4.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Use this section to tell people how to report a vulnerability.
|
||||
|
||||
Tell them where to go, how often they can expect to get an update on a
|
||||
reported vulnerability, what to expect if the vulnerability is accepted or
|
||||
declined, etc.
|
@ -117,3 +117,19 @@ new QTableMetaData().withName("flights").withFields(List.of(
|
||||
.withBehavior(new DateTimeDisplayValueBehavior()
|
||||
.withDefaultZoneId("UTC"))
|
||||
----
|
||||
|
||||
===== CaseChangeBehavior
|
||||
A field can be made to always go through a toUpperCase or toLowerCase transformation, both before it is stored in a backend,
|
||||
and after it is read from a backend, by adding a CaseChangeBehavior to it:
|
||||
|
||||
[source,java]
|
||||
.Examples of using CaseChangeBehavior
|
||||
----
|
||||
new QTableMetaData().withName("item").withFields(List.of(
|
||||
|
||||
new QFieldMetaData("sku", QFieldType.STRING)
|
||||
.withBehavior(CaseChangeBehavior.TO_UPPER_CASE)),
|
||||
|
||||
new QFieldMetaData("username", QFieldType.STRING)
|
||||
.withBehavior(CaseChangeBehavior.TO_LOWER_CASE)),
|
||||
----
|
||||
|
7
pom.xml
7
pom.xml
@ -46,7 +46,7 @@
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<revision>0.20.0-SNAPSHOT</revision>
|
||||
<revision>0.20.0</revision>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
@ -55,7 +55,7 @@
|
||||
<maven.compiler.showDeprecation>true</maven.compiler.showDeprecation>
|
||||
<maven.compiler.showWarnings>true</maven.compiler.showWarnings>
|
||||
<coverage.haltOnFailure>true</coverage.haltOnFailure>
|
||||
<coverage.instructionCoveredRatioMinimum>0.75</coverage.instructionCoveredRatioMinimum>
|
||||
<coverage.instructionCoveredRatioMinimum>0.80</coverage.instructionCoveredRatioMinimum>
|
||||
<coverage.classCoveredRatioMinimum>0.95</coverage.classCoveredRatioMinimum>
|
||||
<plugin.shade.phase>none</plugin.shade.phase>
|
||||
</properties>
|
||||
@ -150,7 +150,7 @@
|
||||
<dependency>
|
||||
<groupId>com.puppycrawl.tools</groupId>
|
||||
<artifactId>checkstyle</artifactId>
|
||||
<version>9.0</version>
|
||||
<version>10.16.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
@ -209,6 +209,7 @@
|
||||
<productionBranch>main</productionBranch>
|
||||
<developmentBranch>dev</developmentBranch>
|
||||
<versionTagPrefix>version-</versionTagPrefix>
|
||||
<releaseBranchPrefix>rel/</releaseBranchPrefix>
|
||||
</gitFlowConfig>
|
||||
<skipFeatureVersion>true</skipFeatureVersion> <!-- Keep feature names out of versions -->
|
||||
<postReleaseGoals>install</postReleaseGoals> <!-- Let CI run deploys -->
|
||||
|
31
qodana.yaml
Normal file
31
qodana.yaml
Normal file
@ -0,0 +1,31 @@
|
||||
#-------------------------------------------------------------------------------#
|
||||
# Qodana analysis is configured by qodana.yaml file #
|
||||
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
|
||||
#-------------------------------------------------------------------------------#
|
||||
version: "1.0"
|
||||
|
||||
#Specify inspection profile for code analysis
|
||||
profile:
|
||||
name: qodana.starter
|
||||
|
||||
#Enable inspections
|
||||
#include:
|
||||
# - name: <SomeEnabledInspectionId>
|
||||
|
||||
#Disable inspections
|
||||
#exclude:
|
||||
# - name: <SomeDisabledInspectionId>
|
||||
# paths:
|
||||
# - <path/where/not/run/inspection>
|
||||
|
||||
projectJDK: 17 #(Applied in CI/CD pipeline)
|
||||
|
||||
#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
|
||||
#bootstrap: sh ./prepare-qodana.sh
|
||||
|
||||
#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
|
||||
#plugins:
|
||||
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)
|
||||
|
||||
#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
|
||||
linter: jetbrains/qodana-jvm:latest
|
@ -192,6 +192,13 @@
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- bring in a newer version of this lib, which quartz transitively loads through c3p0 -->
|
||||
<dependency>
|
||||
<groupId>com.mchange</groupId>
|
||||
<artifactId>mchange-commons-java</artifactId>
|
||||
<version>0.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Many of the deps we bring in use slf4j. This dep maps slf4j to our logger, log4j -->
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
|
@ -37,6 +37,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.PrefixedDefaultThreadFactory;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -55,7 +56,7 @@ public class ActionHelper
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
private static Integer CORE_THREADS = 8;
|
||||
private static Integer MAX_THREADS = 500;
|
||||
private static ExecutorService executorService = new ThreadPoolExecutor(CORE_THREADS, MAX_THREADS, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());
|
||||
private static ExecutorService executorService = new ThreadPoolExecutor(CORE_THREADS, MAX_THREADS, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), new PrefixedDefaultThreadFactory(ActionHelper.class));
|
||||
|
||||
|
||||
|
||||
|
@ -42,6 +42,7 @@ import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
|
||||
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
|
||||
import com.kingsrook.qqq.backend.core.state.StateType;
|
||||
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
|
||||
import com.kingsrook.qqq.backend.core.utils.PrefixedDefaultThreadFactory;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
@ -65,7 +66,7 @@ public class AsyncJobManager
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
private static Integer CORE_THREADS = 8;
|
||||
private static Integer MAX_THREADS = 500;
|
||||
private static ExecutorService executorService = new ThreadPoolExecutor(CORE_THREADS, MAX_THREADS, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());
|
||||
private static ExecutorService executorService = new ThreadPoolExecutor(CORE_THREADS, MAX_THREADS, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), new PrefixedDefaultThreadFactory(AsyncJobManager.class));
|
||||
|
||||
|
||||
private String forcedJobUUID = null;
|
||||
|
@ -32,6 +32,7 @@ import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeSupplier;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -185,8 +186,7 @@ public class AsyncRecordPipeLoop
|
||||
|
||||
if(recordCount > 0)
|
||||
{
|
||||
LOG.info(String.format("Processed %,d records", recordCount)
|
||||
+ String.format(" at end of job [%s] in %,d ms (%.2f records/second).", jobName, (endTime - jobStartTime), 1000d * (recordCount / (.001d + (endTime - jobStartTime)))));
|
||||
LOG.info("End of job summary", logPair("recordCount", recordCount), logPair("jobName", jobName), logPair("millis", endTime - jobStartTime), logPair("recordsPerSecond", 1000d * (recordCount / (.001d + (endTime - jobStartTime)))));
|
||||
}
|
||||
|
||||
return (recordCount);
|
||||
|
@ -127,7 +127,6 @@ public enum AutomationStatus implements PossibleValueEnum<Integer>
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:indentation")
|
||||
public String getInsertOrUpdate()
|
||||
{
|
||||
return switch(this)
|
||||
|
@ -50,10 +50,7 @@ public interface RecordCustomizerUtilityInterface
|
||||
/*******************************************************************************
|
||||
** Container for an old value and a new value.
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:MethodName")
|
||||
record Change(Serializable oldValue, Serializable newValue)
|
||||
{
|
||||
}
|
||||
record Change(Serializable oldValue, Serializable newValue) {}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
|
@ -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), Charset.defaultCharset()));
|
||||
return (tablePath + "/create#defaultValues=" + URLEncoder.encode(JsonUtils.toJson(defaultValues), Charset.defaultCharset()));
|
||||
}
|
||||
|
||||
|
||||
@ -183,7 +183,6 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -256,7 +256,6 @@ public enum DateTimeGroupBy
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:indentation")
|
||||
public Instant roundDown(Instant instant, ZoneId zoneId)
|
||||
{
|
||||
ZonedDateTime zoned = instant.atZone(zoneId);
|
||||
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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.actions.dashboard.widgets;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.AlertData;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Widget that can add an Alert to a process screen.
|
||||
**
|
||||
** In the process, you'll want values:
|
||||
** - alertType - name of entry in AlertType enum (ERROR, WARNING, SUCCESS)
|
||||
** - alertHtml - html to display inside the alert (other than its icon)
|
||||
*******************************************************************************/
|
||||
public class ProcessAlertWidget extends AbstractWidgetRenderer implements MetaDataProducerInterface<QWidgetMetaData>
|
||||
{
|
||||
public static final String NAME = "ProcessAlertWidget";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
|
||||
{
|
||||
AlertData.AlertType alertType = AlertData.AlertType.WARNING;
|
||||
if(input.getQueryParams().containsKey("alertType"))
|
||||
{
|
||||
alertType = AlertData.AlertType.valueOf(input.getQueryParams().get("alertType"));
|
||||
}
|
||||
|
||||
String html = "Warning";
|
||||
if(input.getQueryParams().containsKey("alertHtml"))
|
||||
{
|
||||
html = input.getQueryParams().get("alertHtml");
|
||||
}
|
||||
|
||||
return (new RenderWidgetOutput(new AlertData(alertType, html)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public QWidgetMetaData produce(QInstance qInstance) throws QException
|
||||
{
|
||||
return new QWidgetMetaData()
|
||||
.withType(WidgetType.ALERT.getType())
|
||||
.withGridColumns(12)
|
||||
.withName(NAME)
|
||||
.withIsCard(false)
|
||||
.withShowReloadButton(false)
|
||||
.withCodeReference(new QCodeReference(getClass()));
|
||||
}
|
||||
}
|
@ -218,6 +218,8 @@ public class MetaDataAction
|
||||
|
||||
metaDataOutput.setEnvironmentValues(metaDataInput.getInstance().getEnvironmentValues());
|
||||
|
||||
metaDataOutput.setHelpContents(metaDataInput.getInstance().getHelpContent());
|
||||
|
||||
// todo post-customization - can do whatever w/ the result if you want?
|
||||
|
||||
return metaDataOutput;
|
||||
|
@ -500,7 +500,6 @@ public class PermissionsHelper
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:indentation")
|
||||
static PermissionSubType getEffectivePermissionSubType(QPermissionRules rules, PermissionSubType originalPermissionSubType)
|
||||
{
|
||||
if(rules == null || rules.getLevel() == null)
|
||||
@ -515,10 +514,10 @@ public class PermissionsHelper
|
||||
if(PrivatePermissionSubType.HAS_ACCESS.equals(originalPermissionSubType))
|
||||
{
|
||||
return switch(rules.getLevel())
|
||||
{
|
||||
case NOT_PROTECTED -> null;
|
||||
default -> PrivatePermissionSubType.HAS_ACCESS;
|
||||
};
|
||||
{
|
||||
case NOT_PROTECTED -> null;
|
||||
default -> PrivatePermissionSubType.HAS_ACCESS;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -527,30 +526,30 @@ public class PermissionsHelper
|
||||
// permission sub-type to what we expect to be set for the table //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return switch(rules.getLevel())
|
||||
{
|
||||
case NOT_PROTECTED -> null;
|
||||
case HAS_ACCESS_PERMISSION -> PrivatePermissionSubType.HAS_ACCESS;
|
||||
case READ_WRITE_PERMISSIONS ->
|
||||
{
|
||||
case NOT_PROTECTED -> null;
|
||||
case HAS_ACCESS_PERMISSION -> PrivatePermissionSubType.HAS_ACCESS;
|
||||
case READ_WRITE_PERMISSIONS ->
|
||||
if(PrivatePermissionSubType.READ.equals(originalPermissionSubType) || PrivatePermissionSubType.WRITE.equals(originalPermissionSubType))
|
||||
{
|
||||
if(PrivatePermissionSubType.READ.equals(originalPermissionSubType) || PrivatePermissionSubType.WRITE.equals(originalPermissionSubType))
|
||||
{
|
||||
yield (originalPermissionSubType);
|
||||
}
|
||||
else if(TablePermissionSubType.INSERT.equals(originalPermissionSubType) || TablePermissionSubType.EDIT.equals(originalPermissionSubType) || TablePermissionSubType.DELETE.equals(originalPermissionSubType))
|
||||
{
|
||||
yield (PrivatePermissionSubType.WRITE);
|
||||
}
|
||||
else if(TablePermissionSubType.READ.equals(originalPermissionSubType))
|
||||
{
|
||||
yield (PrivatePermissionSubType.READ);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException("Unexpected permissionSubType: " + originalPermissionSubType);
|
||||
}
|
||||
yield (originalPermissionSubType);
|
||||
}
|
||||
case READ_INSERT_EDIT_DELETE_PERMISSIONS -> originalPermissionSubType;
|
||||
};
|
||||
else if(TablePermissionSubType.INSERT.equals(originalPermissionSubType) || TablePermissionSubType.EDIT.equals(originalPermissionSubType) || TablePermissionSubType.DELETE.equals(originalPermissionSubType))
|
||||
{
|
||||
yield (PrivatePermissionSubType.WRITE);
|
||||
}
|
||||
else if(TablePermissionSubType.READ.equals(originalPermissionSubType))
|
||||
{
|
||||
yield (PrivatePermissionSubType.READ);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new IllegalStateException("Unexpected permissionSubType: " + originalPermissionSubType);
|
||||
}
|
||||
}
|
||||
case READ_INSERT_EDIT_DELETE_PERMISSIONS -> originalPermissionSubType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.processes;
|
||||
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QBadRequestException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessState;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.state.StateType;
|
||||
import com.kingsrook.qqq.backend.core.state.UUIDAndTypeStateKey;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Action handler for running the cancel step of a qqq process
|
||||
*
|
||||
*******************************************************************************/
|
||||
public class CancelProcessAction extends RunProcessAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(CancelProcessAction.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunProcessOutput execute(RunProcessInput runProcessInput) throws QException
|
||||
{
|
||||
ActionHelper.validateSession(runProcessInput);
|
||||
|
||||
QProcessMetaData process = runProcessInput.getInstance().getProcess(runProcessInput.getProcessName());
|
||||
if(process == null)
|
||||
{
|
||||
throw new QBadRequestException("Process [" + runProcessInput.getProcessName() + "] is not defined in this instance.");
|
||||
}
|
||||
|
||||
if(runProcessInput.getProcessUUID() == null)
|
||||
{
|
||||
throw (new QBadRequestException("Cannot cancel process - processUUID was not given."));
|
||||
}
|
||||
|
||||
UUIDAndTypeStateKey stateKey = new UUIDAndTypeStateKey(UUID.fromString(runProcessInput.getProcessUUID()), StateType.PROCESS_STATUS);
|
||||
Optional<ProcessState> processState = getState(runProcessInput.getProcessUUID());
|
||||
if(processState.isEmpty())
|
||||
{
|
||||
throw (new QBadRequestException("Cannot cancel process - State for process UUID [" + runProcessInput.getProcessUUID() + "] was not found."));
|
||||
}
|
||||
|
||||
RunProcessOutput runProcessOutput = new RunProcessOutput();
|
||||
try
|
||||
{
|
||||
if(process.getCancelStep() != null)
|
||||
{
|
||||
LOG.info("Running cancel step for process", logPair("processName", process.getName()));
|
||||
runBackendStep(runProcessInput, process, runProcessOutput, stateKey, process.getCancelStep(), process, processState.get());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Process does not have a custom cancel step to run.", logPair("processName", process.getName()));
|
||||
}
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// upon exception (e.g., one thrown by a step), throw it. //
|
||||
////////////////////////////////////////////////////////////
|
||||
throw (qe);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error cancelling process", e));
|
||||
}
|
||||
finally
|
||||
{
|
||||
//////////////////////////////////////////////////////
|
||||
// always put the final state in the process result //
|
||||
//////////////////////////////////////////////////////
|
||||
runProcessOutput.setProcessState(processState.get());
|
||||
}
|
||||
|
||||
return (runProcessOutput);
|
||||
}
|
||||
|
||||
}
|
@ -26,6 +26,7 @@ import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
@ -34,6 +35,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
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;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
@ -71,7 +73,17 @@ public class RunBackendStepAction
|
||||
QStepMetaData stepMetaData = process.getStep(runBackendStepInput.getStepName());
|
||||
if(stepMetaData == null)
|
||||
{
|
||||
throw new QException("Step [" + runBackendStepInput.getStepName() + "] is not defined in the process [" + process.getName() + "]");
|
||||
if(process.getCancelStep() != null && Objects.equals(process.getCancelStep().getName(), runBackendStepInput.getStepName()))
|
||||
{
|
||||
/////////////////////////////////////
|
||||
// special case for cancel step... //
|
||||
/////////////////////////////////////
|
||||
stepMetaData = process.getCancelStep();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new QException("Step [" + runBackendStepInput.getStepName() + "] is not defined in the process [" + process.getName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
if(!(stepMetaData instanceof QBackendStepMetaData backendStepMetaData))
|
||||
@ -82,7 +94,7 @@ public class RunBackendStepAction
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// ensure input data is set as needed - use callback object to get anything missing //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
ensureRecordsAreInRequest(runBackendStepInput, backendStepMetaData);
|
||||
ensureRecordsAreInRequest(runBackendStepInput, backendStepMetaData, process);
|
||||
ensureInputFieldsAreInRequest(runBackendStepInput, backendStepMetaData);
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
@ -167,7 +179,7 @@ public class RunBackendStepAction
|
||||
** check if this step uses a record list - and if so, if we need to get one
|
||||
** via the callback
|
||||
*******************************************************************************/
|
||||
private void ensureRecordsAreInRequest(RunBackendStepInput runBackendStepInput, QBackendStepMetaData step) throws QException
|
||||
private void ensureRecordsAreInRequest(RunBackendStepInput runBackendStepInput, QBackendStepMetaData step, QProcessMetaData process) throws QException
|
||||
{
|
||||
QFunctionInputMetaData inputMetaData = step.getInputMetaData();
|
||||
if(inputMetaData != null && inputMetaData.getRecordListMetaData() != null)
|
||||
@ -190,9 +202,44 @@ public class RunBackendStepAction
|
||||
|
||||
queryInput.setFilter(callback.getQueryFilter());
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if process has a max-no of records, set a limit on the process of that number plus 1 //
|
||||
// (the plus 1 being so we can see "oh, you selected more than that many; error!" //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(process.getMaxInputRecords() != null)
|
||||
{
|
||||
if(callback.getQueryFilter() == null)
|
||||
{
|
||||
queryInput.setFilter(new QQueryFilter());
|
||||
}
|
||||
|
||||
queryInput.getFilter().setLimit(process.getMaxInputRecords() + 1);
|
||||
}
|
||||
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
runBackendStepInput.setRecords(queryOutput.getRecords());
|
||||
// todo - handle 0 results found?
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// if process defines a max, and more than the max were found, throw an error //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
if(process.getMaxInputRecords() != null)
|
||||
{
|
||||
if(queryOutput.getRecords().size() > process.getMaxInputRecords())
|
||||
{
|
||||
throw (new QUserFacingException("Too many records were selected for this process. At most, only " + process.getMaxInputRecords() + " can be selected."));
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
// if process defines a min, and fewer than the min were found, throw an error //
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
if(process.getMinInputRecords() != null)
|
||||
{
|
||||
if(queryOutput.getRecords().size() < process.getMinInputRecords())
|
||||
{
|
||||
throw (new QUserFacingException("Too few records were selected for this process. At least " + process.getMinInputRecords() + " must be selected."));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ public class RunProcessAction
|
||||
public static final String BASEPULL_THIS_RUNTIME_KEY = "basepullThisRuntimeKey";
|
||||
public static final String BASEPULL_LAST_RUNTIME_KEY = "basepullLastRuntimeKey";
|
||||
public static final String BASEPULL_TIMESTAMP_FIELD = "basepullTimestampField";
|
||||
public static final String BASEPULL_CONFIGURATION = "basepullConfiguration";
|
||||
public static final String BASEPULL_CONFIGURATION = "basepullConfiguration";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// indicator that the timestamp field should be updated - e.g., the execute step is finished. //
|
||||
@ -335,6 +335,13 @@ public class RunProcessAction
|
||||
///////////////////////////////////////////////////
|
||||
runProcessInput.seedFromProcessState(optionalProcessState.get());
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we're restoring an old state, we can discard a previously stored updatedFrontendStepList - //
|
||||
// it is only needed on the transitional edge from a backend-step to a frontend step, but not //
|
||||
// in the other directly //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
optionalProcessState.get().setUpdatedFrontendStepList(null);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// if there were values from the caller, put those (back) in the request //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
@ -357,7 +364,7 @@ public class RunProcessAction
|
||||
/*******************************************************************************
|
||||
** Run a single backend step.
|
||||
*******************************************************************************/
|
||||
private RunBackendStepOutput runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, QProcessMetaData qProcessMetaData, ProcessState processState) throws Exception
|
||||
protected RunBackendStepOutput runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, QProcessMetaData qProcessMetaData, ProcessState processState) throws Exception
|
||||
{
|
||||
RunBackendStepInput runBackendStepInput = new RunBackendStepInput(processState);
|
||||
runBackendStepInput.setProcessName(process.getName());
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.actions.queues;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import com.amazonaws.auth.AWSStaticCredentialsProvider;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
@ -41,6 +42,8 @@ import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSPollerSettings;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
|
||||
@ -90,15 +93,17 @@ public class SQSQueuePoller implements Runnable
|
||||
}
|
||||
queueUrl += queueMetaData.getQueueName();
|
||||
|
||||
while(true)
|
||||
SQSPollerSettings sqsPollerSettings = getSqsPollerSettings(queueProviderMetaData, queueMetaData);
|
||||
|
||||
for(int loop = 0; loop < sqsPollerSettings.getMaxLoops(); loop++)
|
||||
{
|
||||
///////////////////////////////
|
||||
// fetch a batch of messages //
|
||||
///////////////////////////////
|
||||
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest();
|
||||
receiveMessageRequest.setQueueUrl(queueUrl);
|
||||
receiveMessageRequest.setMaxNumberOfMessages(10);
|
||||
receiveMessageRequest.setWaitTimeSeconds(20); // help urge SQS to query multiple servers and find more messages
|
||||
receiveMessageRequest.setMaxNumberOfMessages(sqsPollerSettings.getMaxNumberOfMessages());
|
||||
receiveMessageRequest.setWaitTimeSeconds(sqsPollerSettings.getWaitTimeSeconds()); // larger value (e.g., 20) can help urge SQS to query multiple servers and find more messages
|
||||
ReceiveMessageResult receiveMessageResult = sqs.receiveMessage(receiveMessageRequest);
|
||||
if(receiveMessageResult.getMessages().isEmpty())
|
||||
{
|
||||
@ -177,6 +182,47 @@ public class SQSQueuePoller implements Runnable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a given queueProvider and queue, get the poller settings to use (using
|
||||
** default values if none are set at either level).
|
||||
*******************************************************************************/
|
||||
static SQSPollerSettings getSqsPollerSettings(SQSQueueProviderMetaData queueProviderMetaData, QQueueMetaData queueMetaData)
|
||||
{
|
||||
/////////////////////////////////
|
||||
// start with default settings //
|
||||
/////////////////////////////////
|
||||
SQSPollerSettings sqsPollerSettings = new SQSPollerSettings()
|
||||
.withMaxLoops(Integer.MAX_VALUE)
|
||||
.withMaxNumberOfMessages(10)
|
||||
.withWaitTimeSeconds(20);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// if the queue provider has settings, let them overwrite defaults //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
if(queueProviderMetaData != null && queueProviderMetaData.getPollerSettings() != null)
|
||||
{
|
||||
SQSPollerSettings providerSettings = queueProviderMetaData.getPollerSettings();
|
||||
sqsPollerSettings.setMaxLoops(Objects.requireNonNullElse(providerSettings.getMaxLoops(), sqsPollerSettings.getMaxLoops()));
|
||||
sqsPollerSettings.setMaxNumberOfMessages(Objects.requireNonNullElse(providerSettings.getMaxNumberOfMessages(), sqsPollerSettings.getMaxNumberOfMessages()));
|
||||
sqsPollerSettings.setWaitTimeSeconds(Objects.requireNonNullElse(providerSettings.getWaitTimeSeconds(), sqsPollerSettings.getWaitTimeSeconds()));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
// if the queue has settings, let them overwrite defaults //
|
||||
////////////////////////////////////////////////////////////
|
||||
if(queueMetaData instanceof SQSQueueMetaData sqsQueueMetaData && sqsQueueMetaData.getPollerSettings() != null)
|
||||
{
|
||||
SQSPollerSettings providerSettings = sqsQueueMetaData.getPollerSettings();
|
||||
sqsPollerSettings.setMaxLoops(Objects.requireNonNullElse(providerSettings.getMaxLoops(), sqsPollerSettings.getMaxLoops()));
|
||||
sqsPollerSettings.setMaxNumberOfMessages(Objects.requireNonNullElse(providerSettings.getMaxNumberOfMessages(), sqsPollerSettings.getMaxNumberOfMessages()));
|
||||
sqsPollerSettings.setWaitTimeSeconds(Objects.requireNonNullElse(providerSettings.getWaitTimeSeconds(), sqsPollerSettings.getWaitTimeSeconds()));
|
||||
}
|
||||
|
||||
return sqsPollerSettings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queueProviderMetaData
|
||||
**
|
||||
|
@ -44,6 +44,7 @@ import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@ -216,7 +217,8 @@ public class ExportAction
|
||||
}
|
||||
queryInput.getFilter().setLimit(exportInput.getLimit());
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.withQueryHint(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
||||
queryInput.withQueryHint(QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
||||
queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// tell this query that it needs to put its output into a pipe //
|
||||
|
@ -59,6 +59,7 @@ import com.kingsrook.qqq.backend.core.model.actions.reporting.ExportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportFormat;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.reporting.ReportOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
|
||||
@ -417,7 +418,8 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
queryInput.setTableName(dataSource.getSourceTable());
|
||||
queryInput.setFilter(queryFilter);
|
||||
queryInput.setQueryJoins(dataSource.getQueryJoins());
|
||||
queryInput.withQueryHint(QueryInput.QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
||||
queryInput.withQueryHint(QueryHint.POTENTIALLY_LARGE_NUMBER_OF_RESULTS);
|
||||
queryInput.withQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource, new JoinsContext(reportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), queryInput.getFilter())));
|
||||
|
@ -77,7 +77,6 @@ public class ExecuteCodeAction
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:indentation")
|
||||
public void run(ExecuteCodeInput input, ExecuteCodeOutput output) throws QException, QCodeException
|
||||
{
|
||||
QCodeReference codeReference = input.getCodeReference();
|
||||
|
@ -94,7 +94,7 @@ public class BuildScriptLogAndScriptLogLineExecutionLogger implements QCodeExecu
|
||||
protected QRecord buildDetailLogRecord(String logLine)
|
||||
{
|
||||
return (new QRecord()
|
||||
.withValue("scriptLogId", scriptLog.getValue("id"))
|
||||
.withValue("scriptLogId", scriptLog == null ? null : scriptLog.getValue("id"))
|
||||
.withValue("timestamp", Instant.now())
|
||||
.withValue("text", truncate(logLine)));
|
||||
}
|
||||
@ -145,6 +145,14 @@ public class BuildScriptLogAndScriptLogLineExecutionLogger implements QCodeExecu
|
||||
{
|
||||
this.executeCodeInput = executeCodeInput;
|
||||
this.scriptLog = buildHeaderRecord(executeCodeInput);
|
||||
|
||||
if(scriptLogLines != null)
|
||||
{
|
||||
for(QRecord scriptLogLine : scriptLogLines)
|
||||
{
|
||||
scriptLogLine.setValue("scriptLogId", scriptLog.getValue("id"));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -22,9 +22,12 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import java.util.Collections;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.AggregateInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
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.aggregate.AggregateInput;
|
||||
@ -58,6 +61,11 @@ public class AggregateAction
|
||||
QTableMetaData table = aggregateInput.getTable();
|
||||
QBackendMetaData backend = aggregateInput.getBackend();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// apply any available field behaviors to the filter (noting that, if anything changes, a new filter is returned) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
aggregateInput.setFilter(ValueBehaviorApplier.applyFieldBehaviorsToFilter(QContext.getQInstance(), table, aggregateInput.getFilter(), Collections.emptySet()));
|
||||
|
||||
QueryStat queryStat = QueryStatManager.newQueryStat(backend, table, aggregateInput.getFilter());
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
@ -67,6 +75,10 @@ public class AggregateAction
|
||||
aggregateInterface.setQueryStat(queryStat);
|
||||
AggregateOutput aggregateOutput = aggregateInterface.execute(aggregateInput);
|
||||
|
||||
// todo, maybe, not real important? ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.READ, QContext.getQInstance(), table, aggregateOutput.getResults(), null);
|
||||
// issue being, the signature there... it takes a list of QRecords, which aren't what we have...
|
||||
// do we want to ... idk, refactor all these behavior deals? hmm... maybe a new interface/ for ones that do reads? not sure.
|
||||
|
||||
QueryStatManager.getInstance().add(queryStat);
|
||||
|
||||
return aggregateOutput;
|
||||
|
@ -22,9 +22,12 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import java.util.Collections;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.CountInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
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.count.CountInput;
|
||||
@ -58,6 +61,11 @@ public class CountAction
|
||||
QTableMetaData table = countInput.getTable();
|
||||
QBackendMetaData backend = countInput.getBackend();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// apply any available field behaviors to the filter (noting that, if anything changes, a new filter is returned) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
countInput.setFilter(ValueBehaviorApplier.applyFieldBehaviorsToFilter(QContext.getQInstance(), table, countInput.getFilter(), Collections.emptySet()));
|
||||
|
||||
QueryStat queryStat = QueryStatManager.newQueryStat(backend, table, countInput.getFilter());
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
|
@ -23,6 +23,8 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@ -34,8 +36,10 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.GetInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.GetActionCacheHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
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.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
@ -45,11 +49,16 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
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.metadata.fields.AdornmentType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldFilterBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -58,20 +67,14 @@ import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||
*******************************************************************************/
|
||||
public class GetAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(GetAction.class);
|
||||
|
||||
private Optional<TableCustomizerInterface> postGetRecordCustomizer;
|
||||
|
||||
private GetInput getInput;
|
||||
private QPossibleValueTranslator qPossibleValueTranslator;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QRecord executeForRecord(GetInput getInput) throws QException
|
||||
{
|
||||
return (execute(getInput).getRecord());
|
||||
}
|
||||
private Memoization<Pair<String, String>, List<FieldFilterBehavior<?>>> getFieldFilterBehaviorMemoization = new Memoization<>();
|
||||
|
||||
|
||||
|
||||
@ -108,13 +111,15 @@ public class GetAction
|
||||
}
|
||||
|
||||
GetOutput getOutput;
|
||||
boolean usingDefaultGetInterface = false;
|
||||
boolean usingDefaultGetInterface = false;
|
||||
if(getInterface == null)
|
||||
{
|
||||
getInterface = new DefaultGetInterface();
|
||||
usingDefaultGetInterface = true;
|
||||
}
|
||||
|
||||
getInput = applyFieldBehaviors(getInput);
|
||||
|
||||
getInterface.validateInput(getInput);
|
||||
getOutput = getInterface.execute(getInput);
|
||||
|
||||
@ -140,6 +145,119 @@ public class GetAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private GetInput applyFieldBehaviors(GetInput getInput)
|
||||
{
|
||||
QTableMetaData table = getInput.getTable();
|
||||
|
||||
try
|
||||
{
|
||||
if(getInput.getPrimaryKey() != null)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the input has a primary key, get its behaviors, then apply, and update the pkey in the input if the value is different //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<FieldFilterBehavior<?>> fieldFilterBehaviors = getFieldFilterBehaviors(table, table.getPrimaryKeyField());
|
||||
for(FieldFilterBehavior<?> fieldFilterBehavior : CollectionUtils.nonNullList(fieldFilterBehaviors))
|
||||
{
|
||||
QFilterCriteria pkeyCriteria = new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.EQUALS, getInput.getPrimaryKey());
|
||||
QFilterCriteria updatedCriteria = ValueBehaviorApplier.apply(pkeyCriteria, QContext.getQInstance(), table, table.getField(table.getPrimaryKeyField()), fieldFilterBehavior);
|
||||
if(updatedCriteria != pkeyCriteria)
|
||||
{
|
||||
getInput.setPrimaryKey(updatedCriteria.getValues().get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(getInput.getUniqueKey() != null)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the input has a unique key, get its behaviors, then apply, and update the ukey values in the input if any are different //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Map<String, Serializable> updatedUniqueKey = new HashMap<>(getInput.getUniqueKey());
|
||||
for(String fieldName : getInput.getUniqueKey().keySet())
|
||||
{
|
||||
List<FieldFilterBehavior<?>> fieldFilterBehaviors = getFieldFilterBehaviors(table, fieldName);
|
||||
for(FieldFilterBehavior<?> fieldFilterBehavior : CollectionUtils.nonNullList(fieldFilterBehaviors))
|
||||
{
|
||||
QFilterCriteria ukeyCriteria = new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, updatedUniqueKey.get(fieldName));
|
||||
QFilterCriteria updatedCriteria = ValueBehaviorApplier.apply(ukeyCriteria, QContext.getQInstance(), table, table.getField(table.getPrimaryKeyField()), fieldFilterBehavior);
|
||||
updatedUniqueKey.put(fieldName, updatedCriteria.getValues().get(0));
|
||||
}
|
||||
}
|
||||
getInput.setUniqueKey(updatedUniqueKey);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error applying field behaviors to get input - will run with original inputs", e);
|
||||
}
|
||||
|
||||
return (getInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<FieldFilterBehavior<?>> getFieldFilterBehaviors(QTableMetaData tableMetaData, String fieldName)
|
||||
{
|
||||
Pair<String, String> key = new Pair<>(tableMetaData.getName(), fieldName);
|
||||
return getFieldFilterBehaviorMemoization.getResult(key, (p) ->
|
||||
{
|
||||
List<FieldFilterBehavior<?>> rs = new ArrayList<>();
|
||||
for(FieldBehavior<?> fieldBehavior : CollectionUtils.nonNullCollection(tableMetaData.getFields().get(fieldName).getBehaviors()))
|
||||
{
|
||||
if(fieldBehavior instanceof FieldFilterBehavior<?> fieldFilterBehavior)
|
||||
{
|
||||
rs.add(fieldFilterBehavior);
|
||||
}
|
||||
}
|
||||
return (rs);
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** shorthand way to call for the most common use-case, when you just want the
|
||||
** output record to be returned.
|
||||
*******************************************************************************/
|
||||
public QRecord executeForRecord(GetInput getInput) throws QException
|
||||
{
|
||||
return (execute(getInput).getRecord());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** more shorthand way to call for the most common use-case, when you just want the
|
||||
** output record to be returned, and you just want to pass in a table name and primary key.
|
||||
*******************************************************************************/
|
||||
public static QRecord execute(String tableName, Serializable primaryKey) throws QException
|
||||
{
|
||||
GetAction getAction = new GetAction();
|
||||
GetInput getInput = new GetInput(tableName).withPrimaryKey(primaryKey);
|
||||
return getAction.executeForRecord(getInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** more shorthand way to call for the most common use-case, when you just want the
|
||||
** output record to be returned, and you just want to pass in a table name and unique key
|
||||
*******************************************************************************/
|
||||
public static QRecord execute(String tableName, Map<String, Serializable> uniqueKey) throws QException
|
||||
{
|
||||
GetAction getAction = new GetAction();
|
||||
GetInput getInput = new GetInput(tableName).withUniqueKey(uniqueKey);
|
||||
return getAction.executeForRecord(getInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Run a GetAction by using the QueryAction instead (e.g., with a filter made
|
||||
** from the pkey/ukey, and returning the single record if found).
|
||||
@ -228,6 +346,8 @@ public class GetAction
|
||||
returnRecord = postGetRecordCustomizer.get().postQuery(getInput, List.of(record)).get(0);
|
||||
}
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.READ, QContext.getQInstance(), getInput.getTable(), List.of(record), null);
|
||||
|
||||
if(getInput.getShouldTranslatePossibleValues())
|
||||
{
|
||||
if(qPossibleValueTranslator == null)
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -41,6 +42,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryActionCacheHel
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
@ -117,6 +119,11 @@ public class QueryAction
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// apply any available field behaviors to the filter (noting that, if anything changes, a new filter is returned) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
queryInput.setFilter(ValueBehaviorApplier.applyFieldBehaviorsToFilter(QContext.getQInstance(), table, queryInput.getFilter(), Collections.emptySet()));
|
||||
|
||||
QueryStat queryStat = QueryStatManager.newQueryStat(backend, table, queryInput.getFilter());
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
@ -151,6 +158,22 @@ public class QueryAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** shorthand way to call for the most common use-case, when you just want the
|
||||
** records to be returned, and you just want to pass in a table name and filter.
|
||||
*******************************************************************************/
|
||||
public static List<QRecord> execute(String tableName, QQueryFilter filter) throws QException
|
||||
{
|
||||
QueryAction queryAction = new QueryAction();
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setFilter(filter);
|
||||
QueryOutput queryOutput = queryAction.execute(queryInput);
|
||||
return (queryOutput.getRecords());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -268,6 +291,8 @@ public class QueryAction
|
||||
records = postQueryRecordCustomizer.get().postQuery(queryInput, records);
|
||||
}
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(ValueBehaviorApplier.Action.READ, QContext.getQInstance(), queryInput.getTable(), records, null);
|
||||
|
||||
if(queryInput.getShouldTranslatePossibleValues())
|
||||
{
|
||||
if(qPossibleValueTranslator == null)
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -335,6 +336,9 @@ public class UpdateAction
|
||||
QTableMetaData table = updateInput.getTable();
|
||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// todo - evolve to use lock tree (e.g., from multi-locks) //
|
||||
/////////////////////////////////////////////////////////////
|
||||
List<RecordSecurityLock> onlyWriteLocks = RecordSecurityLockFilters.filterForOnlyWriteLocks(CollectionUtils.nonNullList(table.getRecordSecurityLocks()));
|
||||
|
||||
for(List<QRecord> page : CollectionUtils.getPages(updateInput.getRecords(), 1000))
|
||||
@ -395,7 +399,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);
|
||||
List<QErrorMessage> errors = ValidateRecordSecurityLockHelper.validateRecordSecurityValue(table, lock, lockValue, fieldType, ValidateRecordSecurityLockHelper.Action.UPDATE, Collections.emptyMap());
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
errors.forEach(e -> record.addError(e));
|
||||
|
@ -23,8 +23,10 @@ package com.kingsrook.qqq.backend.core.actions.tables.helpers;
|
||||
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.kingsrook.qqq.backend.core.utils.PrefixedDefaultThreadFactory;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -50,6 +52,9 @@ public class ActionTimeoutHelper
|
||||
|
||||
private boolean didTimeout = false;
|
||||
|
||||
private static Integer CORE_THREADS = 10;
|
||||
private static ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(CORE_THREADS, new PrefixedDefaultThreadFactory(ActionTimeoutHelper.class));
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -75,7 +80,7 @@ public class ActionTimeoutHelper
|
||||
return;
|
||||
}
|
||||
|
||||
future = Executors.newSingleThreadScheduledExecutor().schedule(() ->
|
||||
future = scheduledExecutorService.schedule(() ->
|
||||
{
|
||||
didTimeout = true;
|
||||
runnable.run();
|
||||
|
@ -57,6 +57,7 @@ import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTable;
|
||||
import com.kingsrook.qqq.backend.core.model.tables.QQQTablesMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.PrefixedDefaultThreadFactory;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
@ -176,7 +177,7 @@ public class QueryStatManager
|
||||
active = true;
|
||||
queryStats = new ArrayList<>();
|
||||
|
||||
executorService = Executors.newSingleThreadScheduledExecutor();
|
||||
executorService = Executors.newSingleThreadScheduledExecutor(new PrefixedDefaultThreadFactory(this));
|
||||
executorService.scheduleAtFixedRate(new QueryStatManagerInsertJob(), jobInitialDelay, jobPeriodSeconds, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
// actually check lock values //
|
||||
////////////////////////////////
|
||||
Map<Serializable, RecordWithErrors> errorRecords = new HashMap<>();
|
||||
evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>());
|
||||
evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>(), madeUpPrimaryKeys);
|
||||
|
||||
/////////////////////////////////
|
||||
// propagate errors to records //
|
||||
@ -141,7 +141,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) 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) throws QException
|
||||
{
|
||||
if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock)
|
||||
{
|
||||
@ -152,7 +152,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
for(RecordSecurityLock childLock : CollectionUtils.nonNullList(multiRecordSecurityLock.getLocks()))
|
||||
{
|
||||
treePosition.add(i);
|
||||
evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition);
|
||||
evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition, madeUpPrimaryKeys);
|
||||
treePosition.remove(treePosition.size() - 1);
|
||||
i++;
|
||||
}
|
||||
@ -192,7 +192,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
}
|
||||
|
||||
Serializable recordSecurityValue = record.getValue(field.getName());
|
||||
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action);
|
||||
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);
|
||||
@ -337,7 +337,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
|
||||
for(QRecord inputRecord : inputRecords)
|
||||
{
|
||||
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action);
|
||||
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);
|
||||
@ -370,14 +370,14 @@ public class ValidateRecordSecurityLockHelper
|
||||
{
|
||||
String primaryKeyField = table.getPrimaryKeyField();
|
||||
Map<Serializable, QRecord> madeUpPrimaryKeys = new HashMap<>();
|
||||
Integer madeUpPrimaryKey = -1;
|
||||
Integer madeUpPrimaryKey = Integer.MIN_VALUE / 2;
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(record.getValue(primaryKeyField) == null)
|
||||
{
|
||||
madeUpPrimaryKeys.put(madeUpPrimaryKey, record);
|
||||
record.setValue(primaryKeyField, madeUpPrimaryKey);
|
||||
madeUpPrimaryKey--;
|
||||
madeUpPrimaryKey++;
|
||||
}
|
||||
}
|
||||
return madeUpPrimaryKeys;
|
||||
@ -390,7 +390,6 @@ public class ValidateRecordSecurityLockHelper
|
||||
** MultiRecordSecurityLock, with only the appropriate lock-scopes being included
|
||||
** (e.g., read-locks for selects, write-locks for insert/update/delete).
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:Indentation")
|
||||
static MultiRecordSecurityLock getRecordSecurityLocks(QTableMetaData table, Action action)
|
||||
{
|
||||
List<RecordSecurityLock> allLocksOnTable = CollectionUtils.nonNullList(table.getRecordSecurityLocks());
|
||||
@ -445,9 +444,9 @@ public class ValidateRecordSecurityLockHelper
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<QErrorMessage> validateRecordSecurityValue(QTableMetaData table, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action)
|
||||
public static List<QErrorMessage> validateRecordSecurityValue(QTableMetaData table, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action, Map<Serializable, QRecord> madeUpPrimaryKeys)
|
||||
{
|
||||
if(recordSecurityValue == null)
|
||||
if(recordSecurityValue == null || (madeUpPrimaryKeys != null && madeUpPrimaryKeys.containsKey(recordSecurityValue)))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// handle null values - error if the NullValueBehavior is DENY //
|
||||
|
@ -32,13 +32,12 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
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.QQueryFilter;
|
||||
@ -78,44 +77,6 @@ public class QPossibleValueTranslator
|
||||
|
||||
private int maxSizePerPvsCache = 50_000;
|
||||
|
||||
private Map<String, QBackendTransaction> transactionsPerTable = new HashMap<>();
|
||||
|
||||
// todo not commit - remove instance & session - use Context
|
||||
|
||||
|
||||
boolean useTransactionsAsConnectionPool = false;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private QBackendTransaction getTransaction(String tableName)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////
|
||||
// mmm, this does cut down on connections used - //
|
||||
// especially seems helpful in big exports. //
|
||||
// but, let's just start using connection pools instead... //
|
||||
/////////////////////////////////////////////////////////////
|
||||
if(useTransactionsAsConnectionPool)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!transactionsPerTable.containsKey(tableName))
|
||||
{
|
||||
transactionsPerTable.put(tableName, QBackendTransaction.openFor(new InsertInput(tableName)));
|
||||
}
|
||||
|
||||
return (transactionsPerTable.get(tableName));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error opening transaction for table", logPair("tableName", tableName));
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -421,7 +382,6 @@ public class QPossibleValueTranslator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:Indentation")
|
||||
private String doFormatPossibleValue(String formatString, List<String> valueFields, Object id, String label)
|
||||
{
|
||||
List<Object> values = new ArrayList<>();
|
||||
@ -601,7 +561,7 @@ public class QPossibleValueTranslator
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(idField, QCriteriaOperator.IN, page)));
|
||||
queryInput.setTransaction(getTransaction(tableName));
|
||||
queryInput.hasQueryHint(QueryHint.MAY_USE_READ_ONLY_BACKEND);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// when querying for possible values, we do want to generate their display values, which makes record labels, which are usually used as PVS labels //
|
||||
|
@ -22,12 +22,18 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.values;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
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.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldDisplayBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldFilterBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
@ -46,6 +52,7 @@ public class ValueBehaviorApplier
|
||||
{
|
||||
INSERT,
|
||||
UPDATE,
|
||||
READ,
|
||||
FORMATTING
|
||||
}
|
||||
|
||||
@ -97,4 +104,169 @@ public class ValueBehaviorApplier
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** apply field behaviors (of FieldFilterBehavior type) to a QQueryFilter.
|
||||
** note that, we don't like to ever edit a QQueryFilter itself (e.g., as it might
|
||||
** have come from meta-data, or it might have some immutable structures in it).
|
||||
** So, if any changes are needed, they'll be returned in a clone.
|
||||
** So, either way, you should use this method like:
|
||||
*
|
||||
** QQueryFilter myFilter = // wherever I got my filter from
|
||||
** myFilter = ValueBehaviorApplier.applyFieldBehaviorsToFilter(QContext.getInstance, table, myFilter, null);
|
||||
** // e.g., always re-assign over top of your filter.
|
||||
*******************************************************************************/
|
||||
public static QQueryFilter applyFieldBehaviorsToFilter(QInstance instance, QTableMetaData table, QQueryFilter filter, Set<FieldBehavior<?>> behaviorsToOmit)
|
||||
{
|
||||
////////////////////////////////////////////////
|
||||
// for null or empty filter, return the input //
|
||||
////////////////////////////////////////////////
|
||||
if(filter == null || !filter.hasAnyCriteria())
|
||||
{
|
||||
return (filter);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// track if we need to make & return a clone. //
|
||||
// which will be the case if we get back any different criteria, //
|
||||
// or any different sub-filters, than what we originally had. //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
boolean needToUseClone = false;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// make a new criteria list, and a new subFilter list - either null, if the source was null, or a new array list //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<QFilterCriteria> newCriteriaList = filter.getCriteria() == null ? null : new ArrayList<>();
|
||||
List<QQueryFilter> newSubFilters = filter.getSubFilters() == null ? null : new ArrayList<>();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// for each criteria, if its field has any applicable behaviors, apply them //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
for(QFilterCriteria criteria : CollectionUtils.nonNullList(filter.getCriteria()))
|
||||
{
|
||||
QFieldMetaData field = table.getFields().get(criteria.getFieldName());
|
||||
if(field == null && criteria.getFieldName() != null && criteria.getFieldName().contains("."))
|
||||
{
|
||||
String[] parts = criteria.getFieldName().split("\\.");
|
||||
if(parts.length == 2)
|
||||
{
|
||||
QTableMetaData joinTable = instance.getTable(parts[0]);
|
||||
if(joinTable != null)
|
||||
{
|
||||
field = joinTable.getFields().get(parts[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QFilterCriteria criteriaToUse = criteria;
|
||||
if(field != null)
|
||||
{
|
||||
for(FieldBehavior<?> fieldBehavior : CollectionUtils.nonNullCollection(field.getBehaviors()))
|
||||
{
|
||||
boolean applyBehavior = true;
|
||||
if(behaviorsToOmit != null && behaviorsToOmit.contains(fieldBehavior))
|
||||
{
|
||||
applyBehavior = false;
|
||||
}
|
||||
|
||||
if(applyBehavior && fieldBehavior instanceof FieldFilterBehavior<?> filterBehavior)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// call to apply the behavior on the criteria - which will return a //
|
||||
// new criteria if any values are changed, else the input criteria //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
criteriaToUse = apply(criteriaToUse, instance, table, field, filterBehavior);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the new criteria is not the same as the old criteria, mark that we need to make and return a clone. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(criteriaToUse != criteria)
|
||||
{
|
||||
needToUseClone = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newCriteriaList.add(criteriaToUse);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// similar to above - iterate over the subfilters, making a recursive call, and tracking if we //
|
||||
// got back the same object (in which case, there are no changes, and we don't need to clone), //
|
||||
// or a different object (in which case, we do need a clone, because there were changes). //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QQueryFilter subFilter : CollectionUtils.nonNullList(filter.getSubFilters()))
|
||||
{
|
||||
QQueryFilter newSubFilter = applyFieldBehaviorsToFilter(instance, table, subFilter, behaviorsToOmit);
|
||||
if(newSubFilter != subFilter)
|
||||
{
|
||||
newSubFilters.add(newSubFilter);
|
||||
needToUseClone = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
newSubFilters.add(subFilter);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we need to return a clone, then do so, replacing the lists with the ones we built in here //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(needToUseClone)
|
||||
{
|
||||
QQueryFilter cloneFilter = filter.clone();
|
||||
cloneFilter.setCriteria(newCriteriaList);
|
||||
cloneFilter.setSubFilters(newSubFilters);
|
||||
return (cloneFilter);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// else, if no clone needed (e.g., no changes), return the original filter //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
return (filter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QFilterCriteria apply(QFilterCriteria criteria, QInstance instance, QTableMetaData table, QFieldMetaData field, FieldFilterBehavior<?> filterBehavior)
|
||||
{
|
||||
if(criteria == null || CollectionUtils.nullSafeIsEmpty(criteria.getValues()))
|
||||
{
|
||||
return (criteria);
|
||||
}
|
||||
|
||||
List<Serializable> newValues = new ArrayList<>();
|
||||
boolean changedAny = false;
|
||||
|
||||
for(Serializable value : criteria.getValues())
|
||||
{
|
||||
Serializable newValue = filterBehavior.applyToFilterCriteriaValue(value, instance, table, field);
|
||||
if(!Objects.equals(value, newValue))
|
||||
{
|
||||
newValues.add(newValue);
|
||||
changedAny = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
newValues.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
if(changedAny)
|
||||
{
|
||||
QFilterCriteria clone = criteria.clone();
|
||||
clone.setValues(newValues);
|
||||
return (clone);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (criteria);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -123,6 +123,18 @@ public class QInstanceEnricher
|
||||
*******************************************************************************/
|
||||
public void enrich()
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// at one point, we did apps later - but - it was possible to put tables in an app's //
|
||||
// sections, but not its children list (enrichApp fixes this by adding such tables to //
|
||||
// the children list) so then when enrichTable runs, it looks for fields that are //
|
||||
// possible-values pointed at tables, for adding LINK adornments - and that could //
|
||||
// cause such links to be omitted, mysteriously! so, do app enrichment before tables. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(qInstance.getApps() != null)
|
||||
{
|
||||
qInstance.getApps().values().forEach(this::enrichApp);
|
||||
}
|
||||
|
||||
if(qInstance.getTables() != null)
|
||||
{
|
||||
qInstance.getTables().values().forEach(this::enrichTable);
|
||||
@ -139,11 +151,6 @@ public class QInstanceEnricher
|
||||
qInstance.getBackends().values().forEach(this::enrichBackend);
|
||||
}
|
||||
|
||||
if(qInstance.getApps() != null)
|
||||
{
|
||||
qInstance.getApps().values().forEach(this::enrichApp);
|
||||
}
|
||||
|
||||
if(qInstance.getReports() != null)
|
||||
{
|
||||
qInstance.getReports().values().forEach(this::enrichReport);
|
||||
@ -384,9 +391,9 @@ public class QInstanceEnricher
|
||||
process.setLabel(nameToLabel(process.getName()));
|
||||
}
|
||||
|
||||
if(process.getStepList() != null)
|
||||
for(QStepMetaData step : CollectionUtils.nonNullMap(process.getAllSteps()).values())
|
||||
{
|
||||
process.getStepList().forEach(this::enrichStep);
|
||||
enrichStep(step);
|
||||
}
|
||||
|
||||
for(QSupplementalProcessMetaData supplementalProcessMetaData : CollectionUtils.nonNullMap(process.getSupplementalMetaData()).values())
|
||||
|
@ -105,14 +105,21 @@ public class QInstanceHelpContentManager
|
||||
for(String part : key.split(";"))
|
||||
{
|
||||
String[] parts = part.split(":");
|
||||
nameValuePairs.put(parts[0], parts[1]);
|
||||
if(parts.length > 1)
|
||||
{
|
||||
nameValuePairs.put(parts[0], parts[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.info("Discarding help content with key that does not contain name:value format", logPair("key", key), logPair("id", record.getValue("id")));
|
||||
}
|
||||
}
|
||||
|
||||
String tableName = nameValuePairs.get("table");
|
||||
String processName = nameValuePairs.get("process");
|
||||
String fieldName = nameValuePairs.get("field");
|
||||
String sectionName = nameValuePairs.get("section");
|
||||
String stepName = nameValuePairs.get("step");
|
||||
String stepName = nameValuePairs.get("step");
|
||||
String widgetName = nameValuePairs.get("widget");
|
||||
String slotName = nameValuePairs.get("slot");
|
||||
|
||||
@ -143,7 +150,7 @@ public class QInstanceHelpContentManager
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
if(StringUtils.hasContent(tableName))
|
||||
{
|
||||
processHelpContentForTable(key, tableName, sectionName, fieldName, roles, helpContent);
|
||||
processHelpContentForTable(key, tableName, sectionName, fieldName, slotName, roles, helpContent);
|
||||
}
|
||||
else if(StringUtils.hasContent(processName))
|
||||
{
|
||||
@ -153,6 +160,10 @@ public class QInstanceHelpContentManager
|
||||
{
|
||||
processHelpContentForWidget(key, widgetName, slotName, roles, helpContent);
|
||||
}
|
||||
else if(nameValuePairs.containsKey("instanceLevel"))
|
||||
{
|
||||
processHelpContentForInstance(key, slotName, roles, helpContent);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -165,7 +176,7 @@ public class QInstanceHelpContentManager
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processHelpContentForTable(String key, String tableName, String sectionName, String fieldName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
private static void processHelpContentForTable(String key, String tableName, String sectionName, String fieldName, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
if(table == null)
|
||||
@ -212,7 +223,21 @@ public class QInstanceHelpContentManager
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.info("Unrecognized key format for table help content", logPair("key", key));
|
||||
if(!StringUtils.hasContent(slotName))
|
||||
{
|
||||
LOG.info("Missing slot name in table-level help content", logPair("key", key));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(helpContent != null)
|
||||
{
|
||||
table.withHelpContent(slotName, helpContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
table.removeHelpContent(slotName, roles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -307,6 +332,30 @@ public class QInstanceHelpContentManager
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void processHelpContentForInstance(String key, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
|
||||
{
|
||||
if(!StringUtils.hasContent(slotName))
|
||||
{
|
||||
LOG.info("Missing slot name in instance-level help content", logPair("key", key));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(helpContent != null)
|
||||
{
|
||||
QContext.getQInstance().withHelpContent(slotName, helpContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
QContext.getQInstance().removeHelpContent(slotName, roles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** add a help content object to a list - replacing an entry in the list with the
|
||||
** same roles if one is found.
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.instances;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Object used to record state of a QInstance having been validated.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum QInstanceValidationState
|
||||
{
|
||||
PENDING,
|
||||
RUNNING,
|
||||
COMPLETE
|
||||
}
|
@ -79,6 +79,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QSupplementalProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.QueueType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.queues.SQSQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportField;
|
||||
@ -109,6 +111,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeLambda;
|
||||
import org.quartz.CronExpression;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -138,19 +141,26 @@ public class QInstanceValidator
|
||||
*******************************************************************************/
|
||||
public void validate(QInstance qInstance) throws QInstanceValidationException
|
||||
{
|
||||
if(qInstance.getHasBeenValidated())
|
||||
if(qInstance.getHasBeenValidated() || qInstance.getValidationIsRunning())
|
||||
{
|
||||
//////////////////////////////////////////
|
||||
// don't re-validate if previously done //
|
||||
//////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// don't re-validate if previously complete or currently running (avoids recursive re-validation chaos!) //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////////
|
||||
// mark validation as running now //
|
||||
////////////////////////////////////
|
||||
QInstanceValidationKey validationKey = new QInstanceValidationKey();
|
||||
qInstance.setValidationIsRunning(validationKey);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// the enricher will build a join graph (if there are any joins). we'd like to only do that //
|
||||
// once, during the enrichment/validation work, so, capture it, and store it back in the instance. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
JoinGraph joinGraph = null;
|
||||
long start = System.currentTimeMillis();
|
||||
try
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -191,6 +201,9 @@ public class QInstanceValidator
|
||||
validateUniqueTopLevelNames(qInstance);
|
||||
|
||||
runPlugins(QInstance.class, qInstance, qInstance);
|
||||
|
||||
long end = System.currentTimeMillis();
|
||||
LOG.info("Validation (and enrichment) performance", logPair("millis", (end - start)));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -202,9 +215,22 @@ public class QInstanceValidator
|
||||
throw (new QInstanceValidationException(errors));
|
||||
}
|
||||
|
||||
QInstanceValidationKey validationKey = new QInstanceValidationKey();
|
||||
qInstance.setHasBeenValidated(validationKey);
|
||||
//////////////////////////////
|
||||
// mark validation complete //
|
||||
//////////////////////////////
|
||||
qInstance.setJoinGraph(validationKey, joinGraph);
|
||||
qInstance.setHasBeenValidated(validationKey);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void revalidate(QInstance qInstance) throws QInstanceValidationException
|
||||
{
|
||||
qInstance.setHasBeenValidated(null);
|
||||
validate(qInstance);
|
||||
}
|
||||
|
||||
|
||||
@ -415,11 +441,30 @@ public class QInstanceValidator
|
||||
|
||||
if(queueProvider instanceof SQSQueueProviderMetaData sqsQueueProvider)
|
||||
{
|
||||
if(queueProvider.getType() != null)
|
||||
{
|
||||
assertCondition(queueProvider.getType().equals(QueueType.SQS), "Inconsistent Type/class given for queueProvider: " + name + " (SQSQueueProviderMetaData is not allowed for type " + queueProvider.getType() + ")");
|
||||
}
|
||||
|
||||
assertCondition(StringUtils.hasContent(sqsQueueProvider.getAccessKey()), "Missing accessKey for SQSQueueProvider: " + name);
|
||||
assertCondition(StringUtils.hasContent(sqsQueueProvider.getSecretKey()), "Missing secretKey for SQSQueueProvider: " + name);
|
||||
assertCondition(StringUtils.hasContent(sqsQueueProvider.getBaseURL()), "Missing baseURL for SQSQueueProvider: " + name);
|
||||
assertCondition(StringUtils.hasContent(sqsQueueProvider.getRegion()), "Missing region for SQSQueueProvider: " + name);
|
||||
}
|
||||
else if(queueProvider.getClass().equals(QQueueProviderMetaData.class))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// this just means a subtype wasn't used, so, it should be allowed //
|
||||
// (unless we had a case where a type required a subtype?) //
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
else
|
||||
{
|
||||
if(queueProvider.getType() != null)
|
||||
{
|
||||
assertCondition(!queueProvider.getType().equals(QueueType.SQS), "Inconsistent Type/class given for queueProvider: " + name + " (" + queueProvider.getClass().getSimpleName() + " is not allowed for type " + queueProvider.getType() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
runPlugins(QQueueProviderMetaData.class, queueProvider, qInstance);
|
||||
});
|
||||
@ -430,7 +475,27 @@ public class QInstanceValidator
|
||||
qInstance.getQueues().forEach((name, queue) ->
|
||||
{
|
||||
assertCondition(Objects.equals(name, queue.getName()), "Inconsistent naming for queue: " + name + "/" + queue.getName() + ".");
|
||||
assertCondition(qInstance.getQueueProvider(queue.getProviderName()) != null, "Unrecognized queue providerName for queue: " + name);
|
||||
|
||||
QQueueProviderMetaData queueProvider = qInstance.getQueueProvider(queue.getProviderName());
|
||||
if(assertCondition(queueProvider != null, "Unrecognized queue providerName for queue: " + name))
|
||||
{
|
||||
if(queue instanceof SQSQueueMetaData)
|
||||
{
|
||||
assertCondition(queueProvider.getType().equals(QueueType.SQS), "Inconsistent class given for queueMetaData: " + name + " (SQSQueueMetaData is not allowed for queue provider of type " + queueProvider.getType() + ")");
|
||||
}
|
||||
else if(queue.getClass().equals(QQueueMetaData.class))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// this just means a subtype wasn't used, so, it should be //
|
||||
// allowed (unless we had a case where a type required a subtype? //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
else
|
||||
{
|
||||
assertCondition(!queueProvider.getType().equals(QueueType.SQS), "Inconsistent class given for queueProvider: " + name + " (" + queue.getClass().getSimpleName() + " is not allowed for type " + queueProvider.getType() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
assertCondition(StringUtils.hasContent(queue.getQueueName()), "Missing queueName for queue: " + name);
|
||||
if(assertCondition(StringUtils.hasContent(queue.getProcessName()), "Missing processName for queue: " + name))
|
||||
{
|
||||
@ -657,17 +722,20 @@ public class QInstanceValidator
|
||||
{
|
||||
if(assertCondition(CollectionUtils.nullSafeHasContents(exposedJoin.getJoinPath()), joinPrefix + "is missing a joinPath."))
|
||||
{
|
||||
joinConnectionsForTable = Objects.requireNonNullElseGet(joinConnectionsForTable, () -> joinGraph.getJoinConnections(table.getName()));
|
||||
|
||||
boolean foundJoinConnection = false;
|
||||
for(JoinGraph.JoinConnectionList joinConnectionList : joinConnectionsForTable)
|
||||
if(joinGraph != null)
|
||||
{
|
||||
if(joinConnectionList.matchesJoinPath(exposedJoin.getJoinPath()))
|
||||
joinConnectionsForTable = Objects.requireNonNullElseGet(joinConnectionsForTable, () -> joinGraph.getJoinConnections(table.getName()));
|
||||
|
||||
boolean foundJoinConnection = false;
|
||||
for(JoinGraph.JoinConnectionList joinConnectionList : joinConnectionsForTable)
|
||||
{
|
||||
foundJoinConnection = true;
|
||||
if(joinConnectionList.matchesJoinPath(exposedJoin.getJoinPath()))
|
||||
{
|
||||
foundJoinConnection = true;
|
||||
}
|
||||
}
|
||||
assertCondition(foundJoinConnection, joinPrefix + "specified a joinPath [" + exposedJoin.getJoinPath() + "] which does not match a valid join connection in the instance.");
|
||||
}
|
||||
assertCondition(foundJoinConnection, joinPrefix + "specified a joinPath [" + exposedJoin.getJoinPath() + "] which does not match a valid join connection in the instance.");
|
||||
|
||||
assertCondition(!usedJoinPaths.contains(exposedJoin.getJoinPath()), tablePrefix + "has more than one join with the joinPath: " + exposedJoin.getJoinPath());
|
||||
usedJoinPaths.add(exposedJoin.getJoinPath());
|
||||
@ -1468,7 +1536,7 @@ public class QInstanceValidator
|
||||
warn("Error loading expectedType for field [" + fieldMetaData.getName() + "] in process [" + processName + "]: " + e.getMessage());
|
||||
}
|
||||
|
||||
validateSimpleCodeReference("Process " + processName + " code reference: ", codeReference, expectedClass);
|
||||
validateSimpleCodeReference("Process " + processName + " code reference:", codeReference, expectedClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1476,6 +1544,14 @@ public class QInstanceValidator
|
||||
}
|
||||
}
|
||||
|
||||
if(process.getCancelStep() != null)
|
||||
{
|
||||
if(assertCondition(process.getCancelStep().getCode() != null, "Cancel step is missing a code reference, in process " + processName))
|
||||
{
|
||||
validateSimpleCodeReference("Process " + processName + " cancel step code reference: ", process.getCancelStep().getCode(), BackendStep.class);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// if the process has a schedule, make sure required schedule data populated //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -1487,7 +1563,11 @@ public class QInstanceValidator
|
||||
|
||||
if(process.getVariantBackend() != null)
|
||||
{
|
||||
assertCondition(qInstance.getBackend(process.getVariantBackend()) != null, "Process " + processName + ", a variant backend was not found named " + process.getVariantBackend());
|
||||
if(qInstance.getBackends() != null)
|
||||
{
|
||||
assertCondition(qInstance.getBackend(process.getVariantBackend()) != null, "Process " + processName + ", a variant backend was not found named " + process.getVariantBackend());
|
||||
}
|
||||
|
||||
assertCondition(process.getVariantRunStrategy() != null, "A variant run strategy was not set for process " + processName + " (which does specify a variant backend)");
|
||||
}
|
||||
else
|
||||
|
@ -23,8 +23,8 @@ package com.kingsrook.qqq.backend.core.model;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -42,7 +42,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
** implement this interface. or, same idea for a QRecordEntity that provides
|
||||
** its own TableMetaData.
|
||||
*******************************************************************************/
|
||||
public interface MetaDataProducerInterface<T extends TopLevelMetaDataInterface>
|
||||
public interface MetaDataProducerInterface<T extends MetaDataProducerOutput>
|
||||
{
|
||||
int DEFAULT_SORT_ORDER = 500;
|
||||
|
||||
|
@ -107,8 +107,10 @@ public class AbstractActionInput
|
||||
/*******************************************************************************
|
||||
** Getter for instance
|
||||
**
|
||||
** Deprecated. Please use QContext.getInstance() instead
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
@Deprecated
|
||||
public QInstance getInstance()
|
||||
{
|
||||
return (QContext.getQInstance());
|
||||
@ -119,8 +121,10 @@ public class AbstractActionInput
|
||||
/*******************************************************************************
|
||||
** Getter for session
|
||||
**
|
||||
** Deprecated. Please use QContext.getSession() instead
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
@Deprecated
|
||||
public QSession getSession()
|
||||
{
|
||||
return (QContext.getQSession());
|
||||
|
@ -32,6 +32,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendProcessMe
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -47,8 +48,9 @@ public class MetaDataOutput extends AbstractActionOutput
|
||||
private Map<String, QFrontendWidgetMetaData> widgets;
|
||||
private Map<String, String> environmentValues;
|
||||
|
||||
private List<AppTreeNode> appTree;
|
||||
private QBrandingMetaData branding;
|
||||
private List<AppTreeNode> appTree;
|
||||
private QBrandingMetaData branding;
|
||||
private Map<String, List<QHelpContent>> helpContents;
|
||||
|
||||
|
||||
|
||||
@ -226,4 +228,25 @@ public class MetaDataOutput extends AbstractActionOutput
|
||||
this.environmentValues = environmentValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for helpContents
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setHelpContents(Map<String, List<QHelpContent>> helpContents)
|
||||
{
|
||||
this.helpContents = helpContents;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for helpContents
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, List<QHelpContent>> getHelpContents()
|
||||
{
|
||||
return helpContents;
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -41,6 +42,11 @@ public class ProcessState implements Serializable
|
||||
private List<String> stepList = new ArrayList<>();
|
||||
private Optional<String> nextStepName = Optional.empty();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// maybe, remove this altogether - just let the frontend compute & send if needed... but how does it know last version...? //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private List<QFrontendStepMetaData> updatedFrontendStepList = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -139,4 +145,36 @@ public class ProcessState implements Serializable
|
||||
{
|
||||
this.stepList = stepList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for updatedFrontendStepList
|
||||
*******************************************************************************/
|
||||
public List<QFrontendStepMetaData> getUpdatedFrontendStepList()
|
||||
{
|
||||
return (this.updatedFrontendStepList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for updatedFrontendStepList
|
||||
*******************************************************************************/
|
||||
public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
|
||||
{
|
||||
this.updatedFrontendStepList = updatedFrontendStepList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for updatedFrontendStepList
|
||||
*******************************************************************************/
|
||||
public ProcessState withUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
|
||||
{
|
||||
this.updatedFrontendStepList = updatedFrontendStepList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -49,8 +49,7 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
|
||||
private ProcessState processState;
|
||||
private Exception exception; // todo - make optional
|
||||
|
||||
private String overrideLastStepName;
|
||||
private List<QFrontendStepMetaData> updatedFrontendStepList = null;
|
||||
private String overrideLastStepName; // todo - does this need to go into state too??
|
||||
|
||||
private List<AuditInput> auditInputList = new ArrayList<>();
|
||||
|
||||
@ -416,7 +415,7 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
|
||||
*******************************************************************************/
|
||||
public List<QFrontendStepMetaData> getUpdatedFrontendStepList()
|
||||
{
|
||||
return (this.updatedFrontendStepList);
|
||||
return (this.processState.getUpdatedFrontendStepList());
|
||||
}
|
||||
|
||||
|
||||
@ -426,18 +425,7 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
|
||||
*******************************************************************************/
|
||||
public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
|
||||
{
|
||||
this.updatedFrontendStepList = updatedFrontendStepList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for updatedFrontendStepList
|
||||
*******************************************************************************/
|
||||
public RunBackendStepOutput withUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
|
||||
{
|
||||
this.updatedFrontendStepList = updatedFrontendStepList;
|
||||
return (this);
|
||||
this.processState.setUpdatedFrontendStepList(updatedFrontendStepList);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -46,8 +46,6 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab
|
||||
private String processUUID;
|
||||
private Optional<Exception> exception = Optional.empty();
|
||||
|
||||
private List<QFrontendStepMetaData> updatedFrontendStepList = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -334,32 +332,21 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for updatedFrontendStepList
|
||||
*******************************************************************************/
|
||||
public List<QFrontendStepMetaData> getUpdatedFrontendStepList()
|
||||
{
|
||||
return (this.updatedFrontendStepList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for updatedFrontendStepList
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
|
||||
{
|
||||
this.updatedFrontendStepList = updatedFrontendStepList;
|
||||
this.processState.setUpdatedFrontendStepList(updatedFrontendStepList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for updatedFrontendStepList
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunProcessOutput withUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
|
||||
public List<QFrontendStepMetaData> getUpdatedFrontendStepList()
|
||||
{
|
||||
this.updatedFrontendStepList = updatedFrontendStepList;
|
||||
return (this);
|
||||
return this.processState.getUpdatedFrontendStepList();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.actions.tables;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Information about the query that an application (or qqq service) may know and
|
||||
** want to tell the backend, that can help influence how the backend processes
|
||||
** query.
|
||||
**
|
||||
** For example, a query with potentially a large result set, for MySQL backend,
|
||||
** we may want to configure the result set to stream results rather than do its
|
||||
** default in-memory thing. See RDBMSQueryAction for usage.
|
||||
*******************************************************************************/
|
||||
public enum QueryHint
|
||||
{
|
||||
POTENTIALLY_LARGE_NUMBER_OF_RESULTS,
|
||||
MAY_USE_READ_ONLY_BACKEND
|
||||
}
|
@ -23,8 +23,10 @@ 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.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
|
||||
@ -44,6 +46,8 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
|
||||
private List<QueryJoin> queryJoins = null;
|
||||
|
||||
private EnumSet<QueryHint> queryHints = EnumSet.noneOf(QueryHint.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -302,4 +306,78 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryHints
|
||||
*******************************************************************************/
|
||||
public EnumSet<QueryHint> getQueryHints()
|
||||
{
|
||||
return (this.queryHints);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queryHints
|
||||
*******************************************************************************/
|
||||
public void setQueryHints(EnumSet<QueryHint> queryHints)
|
||||
{
|
||||
this.queryHints = queryHints;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryHints
|
||||
*******************************************************************************/
|
||||
public AggregateInput withQueryHints(EnumSet<QueryHint> queryHints)
|
||||
{
|
||||
this.queryHints = queryHints;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryHints
|
||||
*******************************************************************************/
|
||||
public AggregateInput withQueryHint(QueryHint queryHint)
|
||||
{
|
||||
if(this.queryHints == null)
|
||||
{
|
||||
this.queryHints = EnumSet.noneOf(QueryHint.class);
|
||||
}
|
||||
this.queryHints.add(queryHint);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryHints
|
||||
*******************************************************************************/
|
||||
public AggregateInput withoutQueryHint(QueryHint queryHint)
|
||||
{
|
||||
if(this.queryHints != null)
|
||||
{
|
||||
this.queryHints.remove(queryHint);
|
||||
}
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** null-safely check if query hints map contains the specified hint
|
||||
*******************************************************************************/
|
||||
public boolean hasQueryHint(QueryHint queryHint)
|
||||
{
|
||||
if(this.queryHints == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (queryHints.contains(queryHint));
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,10 @@ 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.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryHint;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
|
||||
@ -42,6 +44,8 @@ public class CountInput extends AbstractTableActionInput
|
||||
private List<QueryJoin> queryJoins = null;
|
||||
private Boolean includeDistinctCount = false;
|
||||
|
||||
private EnumSet<QueryHint> queryHints = EnumSet.noneOf(QueryHint.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -207,4 +211,78 @@ public class CountInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryHints
|
||||
*******************************************************************************/
|
||||
public EnumSet<QueryHint> getQueryHints()
|
||||
{
|
||||
return (this.queryHints);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queryHints
|
||||
*******************************************************************************/
|
||||
public void setQueryHints(EnumSet<QueryHint> queryHints)
|
||||
{
|
||||
this.queryHints = queryHints;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryHints
|
||||
*******************************************************************************/
|
||||
public CountInput withQueryHints(EnumSet<QueryHint> queryHints)
|
||||
{
|
||||
this.queryHints = queryHints;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryHints
|
||||
*******************************************************************************/
|
||||
public CountInput withQueryHint(QueryHint queryHint)
|
||||
{
|
||||
if(this.queryHints == null)
|
||||
{
|
||||
this.queryHints = EnumSet.noneOf(QueryHint.class);
|
||||
}
|
||||
this.queryHints.add(queryHint);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryHints
|
||||
*******************************************************************************/
|
||||
public CountInput withoutQueryHint(QueryHint queryHint)
|
||||
{
|
||||
if(this.queryHints != null)
|
||||
{
|
||||
this.queryHints.remove(queryHint);
|
||||
}
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** null-safely check if query hints map contains the specified hint
|
||||
*******************************************************************************/
|
||||
public boolean hasQueryHint(QueryHint queryHint)
|
||||
{
|
||||
if(this.queryHints == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (queryHints.contains(queryHint));
|
||||
}
|
||||
}
|
||||
|
@ -25,10 +25,12 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.get;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.QueryOrGetInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
|
||||
@ -60,6 +62,8 @@ public class GetInput extends AbstractTableActionInput implements QueryOrGetInpu
|
||||
private boolean includeAssociations = false;
|
||||
private Collection<String> associationNamesToInclude = null;
|
||||
|
||||
private EnumSet<QueryHint> queryHints = EnumSet.noneOf(QueryHint.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -462,4 +466,79 @@ public class GetInput extends AbstractTableActionInput implements QueryOrGetInpu
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryHints
|
||||
*******************************************************************************/
|
||||
public EnumSet<QueryHint> getQueryHints()
|
||||
{
|
||||
return (this.queryHints);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for queryHints
|
||||
*******************************************************************************/
|
||||
public void setQueryHints(EnumSet<QueryHint> queryHints)
|
||||
{
|
||||
this.queryHints = queryHints;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryHints
|
||||
*******************************************************************************/
|
||||
public GetInput withQueryHints(EnumSet<QueryHint> queryHints)
|
||||
{
|
||||
this.queryHints = queryHints;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryHints
|
||||
*******************************************************************************/
|
||||
public GetInput withQueryHint(QueryHint queryHint)
|
||||
{
|
||||
if(this.queryHints == null)
|
||||
{
|
||||
this.queryHints = EnumSet.noneOf(QueryHint.class);
|
||||
}
|
||||
this.queryHints.add(queryHint);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for queryHints
|
||||
*******************************************************************************/
|
||||
public GetInput withoutQueryHint(QueryHint queryHint)
|
||||
{
|
||||
if(this.queryHints != null)
|
||||
{
|
||||
this.queryHints.remove(queryHint);
|
||||
}
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** null-safely check if query hints map contains the specified hint
|
||||
*******************************************************************************/
|
||||
public boolean hasQueryHint(QueryHint queryHint)
|
||||
{
|
||||
if(this.queryHints == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (queryHints.contains(queryHint));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -31,12 +31,16 @@ import java.util.Set;
|
||||
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.QueryOrGetInputInterface;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Input data for the Query action
|
||||
**
|
||||
** Todo - maybe make a class between AbstractTableActionInput and {QueryInput,
|
||||
** CountInput, and AggregateInput}, with common attributes for all of these
|
||||
** "read" operations (like, queryHints,
|
||||
*******************************************************************************/
|
||||
public class QueryInput extends AbstractTableActionInput implements QueryOrGetInputInterface, Cloneable
|
||||
{
|
||||
@ -74,22 +78,6 @@ public class QueryInput extends AbstractTableActionInput implements QueryOrGetIn
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Information about the query that an application (or qqq service) may know and
|
||||
** want to tell the backend, that can help influence how the backend processes
|
||||
** query.
|
||||
**
|
||||
** For example, a query with potentially a large result set, for MySQL backend,
|
||||
** we may want to configure the result set to stream results rather than do its
|
||||
** default in-memory thing. See RDBMSQueryAction for usage.
|
||||
*******************************************************************************/
|
||||
public enum QueryHint
|
||||
{
|
||||
POTENTIALLY_LARGE_NUMBER_OF_RESULTS
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -683,4 +671,19 @@ public class QueryInput extends AbstractTableActionInput implements QueryOrGetIn
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** null-safely check if query hints map contains the specified hint
|
||||
*******************************************************************************/
|
||||
public boolean hasQueryHint(QueryHint queryHint)
|
||||
{
|
||||
if(this.queryHints == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (queryHints.contains(queryHint));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ public class QueryOutput extends AbstractActionOutput implements Serializable
|
||||
}
|
||||
else
|
||||
{
|
||||
storage = new QueryOutputList();
|
||||
storage = new QueryOutputList(queryInput);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,10 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -33,15 +36,50 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
*******************************************************************************/
|
||||
class QueryOutputList implements QueryOutputStorageInterface
|
||||
{
|
||||
private List<QRecord> records = new ArrayList<>();
|
||||
private static final QLogger LOG = QLogger.getLogger(QueryOutputList.class);
|
||||
|
||||
private final String tableName;
|
||||
private List<QRecord> records = new ArrayList<>();
|
||||
|
||||
private static int LOG_SIZE_INFO_OVER = 50_000;
|
||||
private static int LOG_SIZE_WARN_OVER = 100_000;
|
||||
private static int LOG_SIZE_ERROR_OVER = 250_000;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QueryOutputList()
|
||||
public QueryOutputList(QueryInput queryInput)
|
||||
{
|
||||
tableName = queryInput.getTableName();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void logSize(int sizeBefore, int sizeAfter)
|
||||
{
|
||||
Level level = null;
|
||||
if(sizeBefore < LOG_SIZE_ERROR_OVER && sizeAfter >= LOG_SIZE_ERROR_OVER)
|
||||
{
|
||||
level = Level.ERROR;
|
||||
}
|
||||
else if(sizeBefore < LOG_SIZE_WARN_OVER && sizeAfter >= LOG_SIZE_WARN_OVER)
|
||||
{
|
||||
level = Level.WARN;
|
||||
}
|
||||
else if(sizeBefore < LOG_SIZE_INFO_OVER && sizeAfter >= LOG_SIZE_INFO_OVER)
|
||||
{
|
||||
level = Level.INFO;
|
||||
}
|
||||
|
||||
if(level != null)
|
||||
{
|
||||
LOG.log(level, "Large number of records in QueryOutputList", new Throwable(), logPair("noRecords", sizeAfter), logPair("tableName", tableName));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -52,7 +90,9 @@ class QueryOutputList implements QueryOutputStorageInterface
|
||||
@Override
|
||||
public void addRecord(QRecord record)
|
||||
{
|
||||
int sizeBefore = this.records.size();
|
||||
records.add(record);
|
||||
logSize(sizeBefore, this.records.size());
|
||||
}
|
||||
|
||||
|
||||
@ -63,7 +103,9 @@ class QueryOutputList implements QueryOutputStorageInterface
|
||||
@Override
|
||||
public void addRecords(List<QRecord> records)
|
||||
{
|
||||
int sizeBefore = this.records.size();
|
||||
this.records.addAll(records);
|
||||
logSize(sizeBefore, this.records.size());
|
||||
}
|
||||
|
||||
|
||||
@ -77,4 +119,36 @@ class QueryOutputList implements QueryOutputStorageInterface
|
||||
return (records);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for LOG_SIZE_INFO_OVER
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setLogSizeInfoOver(int logSizeInfoOver)
|
||||
{
|
||||
QueryOutputList.LOG_SIZE_INFO_OVER = logSizeInfoOver;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for LOG_SIZE_WARN_OVER
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setLogSizeWarnOver(int logSizeWarnOver)
|
||||
{
|
||||
QueryOutputList.LOG_SIZE_WARN_OVER = logSizeWarnOver;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for LOG_SIZE_ERROR_OVER
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setLogSizeErrorOver(int logSizeErrorOver)
|
||||
{
|
||||
QueryOutputList.LOG_SIZE_ERROR_OVER = logSizeErrorOver;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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.dashboard.widgets;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Model containing datastructure expected by frontend filter and columns setup widget
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class FilterAndColumnsSetupData extends QWidgetData
|
||||
{
|
||||
private String tableName;
|
||||
private Boolean allowVariables = false;
|
||||
private Boolean hideColumns = false;
|
||||
private List<String> filterDefaultFieldNames;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData(String tableName, Boolean allowVariables, Boolean hideColumns, List<String> filterDefaultFieldNames)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
this.allowVariables = allowVariables;
|
||||
this.hideColumns = hideColumns;
|
||||
this.filterDefaultFieldNames = filterDefaultFieldNames;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getType()
|
||||
{
|
||||
return WidgetType.FILTER_AND_COLUMNS_SETUP.getType();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return (this.tableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for hideColumns
|
||||
*******************************************************************************/
|
||||
public Boolean getHideColumns()
|
||||
{
|
||||
return (this.hideColumns);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for hideColumns
|
||||
*******************************************************************************/
|
||||
public void setHideColumns(Boolean hideColumns)
|
||||
{
|
||||
this.hideColumns = hideColumns;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for hideColumns
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withHideColumns(Boolean hideColumns)
|
||||
{
|
||||
this.hideColumns = hideColumns;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filterDefaultFieldNames
|
||||
*******************************************************************************/
|
||||
public List<String> getFilterDefaultFieldNames()
|
||||
{
|
||||
return (this.filterDefaultFieldNames);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for filterDefaultFieldNames
|
||||
*******************************************************************************/
|
||||
public void setFilterDefaultFieldNames(List<String> filterDefaultFieldNames)
|
||||
{
|
||||
this.filterDefaultFieldNames = filterDefaultFieldNames;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for filterDefaultFieldNames
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withFilterDefaultFieldNames(List<String> filterDefaultFieldNames)
|
||||
{
|
||||
this.filterDefaultFieldNames = filterDefaultFieldNames;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for allowVariables
|
||||
*******************************************************************************/
|
||||
public Boolean getAllowVariables()
|
||||
{
|
||||
return (this.allowVariables);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for allowVariables
|
||||
*******************************************************************************/
|
||||
public void setAllowVariables(Boolean allowVariables)
|
||||
{
|
||||
this.allowVariables = allowVariables;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for allowVariables
|
||||
*******************************************************************************/
|
||||
public FilterAndColumnsSetupData withAllowVariables(Boolean allowVariables)
|
||||
{
|
||||
this.allowVariables = allowVariables;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -69,7 +69,7 @@ public enum WidgetType
|
||||
DYNAMIC_FORM("dynamicForm"),
|
||||
DATA_BAG_VIEWER("dataBagViewer"),
|
||||
PIVOT_TABLE_SETUP("pivotTableSetup"),
|
||||
REPORT_SETUP("reportSetup"),
|
||||
FILTER_AND_COLUMNS_SETUP("filterAndColumnsSetup"),
|
||||
SCRIPT_VIEWER("scriptViewer");
|
||||
|
||||
|
||||
|
@ -49,6 +49,11 @@ public @interface QField
|
||||
*******************************************************************************/
|
||||
String backendName() default "";
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
boolean isPrimaryKey() default false;
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.data;
|
||||
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Marker - that a piece of code should be ignored (e.g., a field not treated as
|
||||
** a @QField)
|
||||
*******************************************************************************/
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface QIgnore
|
||||
{
|
||||
}
|
@ -35,12 +35,15 @@ import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.QErrorMessage;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang3.SerializationUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
@ -462,6 +465,7 @@ public class QRecord implements Serializable
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for a single field's value
|
||||
**
|
||||
@ -616,6 +620,22 @@ public class QRecord implements Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for errors
|
||||
**
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
public String getErrorsAsString()
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
return StringUtils.join("; ", errors.stream().map(e -> e.getMessage()).toList());
|
||||
}
|
||||
return ("");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for errors
|
||||
**
|
||||
@ -732,6 +752,22 @@ public class QRecord implements Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for warnings
|
||||
**
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
public String getWarningsAsString()
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(warnings))
|
||||
{
|
||||
return StringUtils.join("; ", warnings.stream().map(e -> e.getMessage()).toList());
|
||||
}
|
||||
return ("");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for warnings
|
||||
*******************************************************************************/
|
||||
@ -742,6 +778,18 @@ public class QRecord implements Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluently Add one warning to this record
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QRecord withWarning(QWarningMessage warning)
|
||||
{
|
||||
addWarning(warning);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for warnings
|
||||
*******************************************************************************/
|
||||
|
@ -218,6 +218,7 @@ public abstract class QRecordEntity
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -296,7 +297,19 @@ public abstract class QRecordEntity
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Skipping field without @QField annotation", logPair("class", c.getSimpleName()), logPair("fieldName", fieldName));
|
||||
Optional<QIgnore> ignoreAnnotation = getQIgnoreAnnotation(c, fieldName);
|
||||
Optional<QAssociation> associationAnnotation = getQAssociationAnnotation(c, fieldName);
|
||||
|
||||
if(ignoreAnnotation.isPresent() || associationAnnotation.isPresent())
|
||||
{
|
||||
////////////////////////////////////////////////////////////
|
||||
// silently skip if marked as an association or an ignore //
|
||||
////////////////////////////////////////////////////////////
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Skipping field without @QField annotation", logPair("class", c.getSimpleName()), logPair("fieldName", fieldName));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -360,6 +373,16 @@ public abstract class QRecordEntity
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Optional<QIgnore> getQIgnoreAnnotation(Class<? extends QRecordEntity> c, String ignoreName)
|
||||
{
|
||||
return (getAnnotationOnField(c, QIgnore.class, ignoreName));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -419,9 +442,9 @@ public abstract class QRecordEntity
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!method.getName().equals("getClass"))
|
||||
if(!method.getName().equals("getClass") && method.getAnnotation(QIgnore.class) == null)
|
||||
{
|
||||
LOG.debug("Method [" + method.getName() + "] looks like a getter, but its return type, [" + method.getReturnType() + "], isn't supported.");
|
||||
LOG.debug("Method [" + method.getName() + "] in [" + method.getDeclaringClass().getSimpleName() + "] looks like a getter, but its return type, [" + method.getReturnType().getSimpleName() + "], isn't supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,7 +145,7 @@ public interface QRecordEnum
|
||||
{
|
||||
if(!method.getName().equals("getClass") && !method.getName().equals("getDeclaringClass") && !method.getName().equals("getPossibleValueId"))
|
||||
{
|
||||
LOG.debug("Method [" + method.getName() + "] looks like a getter, but its return type, [" + method.getReturnType() + "], isn't supported.");
|
||||
LOG.debug("Method [" + method.getName() + "] in [" + method.getDeclaringClass().getSimpleName() + "] looks like a getter, but its return type, [" + method.getReturnType().getSimpleName() + "], isn't supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
|
||||
** MetaDataProducerHelper, to put point at a package full of these, and populate
|
||||
** your whole QInstance.
|
||||
*******************************************************************************/
|
||||
public abstract class MetaDataProducer<T extends TopLevelMetaDataInterface> implements MetaDataProducerInterface<T>
|
||||
public abstract class MetaDataProducer<T extends MetaDataProducerOutput> implements MetaDataProducerInterface<T>
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ public class MetaDataProducerHelper
|
||||
{
|
||||
try
|
||||
{
|
||||
TopLevelMetaDataInterface metaData = producer.produce(instance);
|
||||
MetaDataProducerOutput metaData = producer.produce(instance);
|
||||
if(metaData != null)
|
||||
{
|
||||
metaData.addSelfToInstance(instance);
|
||||
|
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Output object for a MetaDataProducer, which contains multiple meta-data
|
||||
** objects.
|
||||
*******************************************************************************/
|
||||
public class MetaDataProducerMultiOutput implements MetaDataProducerOutput
|
||||
{
|
||||
private List<MetaDataProducerOutput> contents;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void addSelfToInstance(QInstance instance)
|
||||
{
|
||||
for(MetaDataProducerOutput metaDataProducerOutput : CollectionUtils.nonNullList(contents))
|
||||
{
|
||||
metaDataProducerOutput.addSelfToInstance(instance);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void add(MetaDataProducerOutput metaDataProducerOutput)
|
||||
{
|
||||
if(contents == null)
|
||||
{
|
||||
contents = new ArrayList<>();
|
||||
}
|
||||
contents.add(metaDataProducerOutput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MetaDataProducerMultiOutput with(MetaDataProducerOutput metaDataProducerOutput)
|
||||
{
|
||||
add(metaDataProducerOutput);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public <T extends MetaDataProducerOutput> List<T> getEach(Class<T> c)
|
||||
{
|
||||
List<T> rs = new ArrayList<>();
|
||||
|
||||
for(MetaDataProducerOutput content : contents)
|
||||
{
|
||||
if(content instanceof MetaDataProducerMultiOutput multiOutput)
|
||||
{
|
||||
rs.addAll(multiOutput.getEach(c));
|
||||
}
|
||||
else if(c.isInstance(content))
|
||||
{
|
||||
rs.add(c.cast(content));
|
||||
}
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface to mark objects that can be produced by a MetaDataProducer.
|
||||
**
|
||||
** These would usually be TopLevelMetaData objects (a table, a process, etc)
|
||||
** but can also be a MetaDataProducerMultiOutput, to produce multiple objects
|
||||
** from one producer.
|
||||
*******************************************************************************/
|
||||
public interface MetaDataProducerOutput
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** call the appropriate methods on a QInstance to add ourselves to it.
|
||||
*******************************************************************************/
|
||||
void addSelfToInstance(QInstance instance);
|
||||
|
||||
}
|
@ -33,7 +33,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
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;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidationKey;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidationState;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||
@ -44,6 +46,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.branding.QBrandingMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNodeType;
|
||||
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.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.messaging.QMessagingProviderMetaData;
|
||||
@ -79,7 +83,7 @@ public class QInstance
|
||||
private QAuthenticationMetaData authentication = null;
|
||||
private QBrandingMetaData branding = null;
|
||||
private Map<String, QAutomationProviderMetaData> automationProviders = new HashMap<>();
|
||||
private Map<String, QMessagingProviderMetaData> messagingProviders = new HashMap<>();
|
||||
private Map<String, QMessagingProviderMetaData> messagingProviders = new HashMap<>();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Important to use LinkedHashmap here, to preserve the order in which entries are added. //
|
||||
@ -100,6 +104,8 @@ public class QInstance
|
||||
|
||||
private Map<String, QSupplementalInstanceMetaData> supplementalMetaData = new LinkedHashMap<>();
|
||||
|
||||
protected Map<String, List<QHelpContent>> helpContent;
|
||||
|
||||
private String deploymentMode;
|
||||
private Map<String, String> environmentValues = new LinkedHashMap<>();
|
||||
private String defaultTimeZoneId = "UTC";
|
||||
@ -107,10 +113,13 @@ public class QInstance
|
||||
private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance();
|
||||
private QAuditRules defaultAuditRules = QAuditRules.defaultInstanceLevelNone();
|
||||
|
||||
// todo - lock down the object (no more changes allowed) after it's been validated?
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - lock down the object (no more changes allowed) after it's been validated? //
|
||||
// if doing so, may need to copy all of the collections into read-only versions... //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@JsonIgnore
|
||||
private boolean hasBeenValidated = false;
|
||||
private QInstanceValidationState validationState = QInstanceValidationState.PENDING;
|
||||
|
||||
private Map<String, String> memoizedTablePaths = new HashMap<>();
|
||||
private Map<String, String> memoizedProcessPaths = new HashMap<>();
|
||||
@ -794,32 +803,58 @@ public class QInstance
|
||||
*******************************************************************************/
|
||||
public boolean getHasBeenValidated()
|
||||
{
|
||||
return hasBeenValidated;
|
||||
return validationState.equals(QInstanceValidationState.COMPLETE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** If pass a QInstanceValidationKey (which can only be instantiated by the validator),
|
||||
** then the hasBeenValidated field will be set to true.
|
||||
** then the validationState will be set to COMPLETE.
|
||||
**
|
||||
** Else, if passed a null, hasBeenValidated will be reset to false - e.g., to
|
||||
** Else, if passed a null, the validationState will be reset to PENDING. e.g., to
|
||||
** re-trigger validation (can be useful in tests).
|
||||
*******************************************************************************/
|
||||
public void setHasBeenValidated(QInstanceValidationKey key)
|
||||
{
|
||||
if(key == null)
|
||||
{
|
||||
this.hasBeenValidated = false;
|
||||
this.validationState = QInstanceValidationState.PENDING;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.hasBeenValidated = true;
|
||||
this.validationState = QInstanceValidationState.COMPLETE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** If pass a QInstanceValidationKey (which can only be instantiated by the validator),
|
||||
** then the validationState set to RUNNING.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setValidationIsRunning(QInstanceValidationKey key)
|
||||
{
|
||||
if(key != null)
|
||||
{
|
||||
this.validationState = QInstanceValidationState.RUNNING;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** check if the instance is currently running validation.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public boolean getValidationIsRunning()
|
||||
{
|
||||
return validationState.equals(QInstanceValidationState.RUNNING);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for branding
|
||||
**
|
||||
@ -1380,4 +1415,74 @@ public class QInstance
|
||||
this.schedulableTypes = schedulableTypes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for helpContent
|
||||
*******************************************************************************/
|
||||
public Map<String, List<QHelpContent>> getHelpContent()
|
||||
{
|
||||
return (this.helpContent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for helpContent
|
||||
*******************************************************************************/
|
||||
public void setHelpContent(Map<String, List<QHelpContent>> helpContent)
|
||||
{
|
||||
this.helpContent = helpContent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for helpContent
|
||||
*******************************************************************************/
|
||||
public QInstance withHelpContent(Map<String, List<QHelpContent>> helpContent)
|
||||
{
|
||||
this.helpContent = helpContent;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for adding 1 helpContent (for a slot)
|
||||
*******************************************************************************/
|
||||
public QInstance withHelpContent(String slot, QHelpContent helpContent)
|
||||
{
|
||||
if(this.helpContent == null)
|
||||
{
|
||||
this.helpContent = new HashMap<>();
|
||||
}
|
||||
|
||||
List<QHelpContent> listForSlot = this.helpContent.computeIfAbsent(slot, (k) -> new ArrayList<>());
|
||||
QInstanceHelpContentManager.putHelpContentInList(helpContent, listForSlot);
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** remove a helpContent for a slot based on its set of roles
|
||||
*******************************************************************************/
|
||||
public void removeHelpContent(String slot, Set<HelpRole> roles)
|
||||
{
|
||||
if(this.helpContent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<QHelpContent> listForSlot = this.helpContent.get(slot);
|
||||
if(listForSlot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, listForSlot);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
public interface TopLevelMetaDataInterface extends MetaDataProducerOutput
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
|
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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 case of string values.
|
||||
*******************************************************************************/
|
||||
public enum CaseChangeBehavior implements FieldBehavior<CaseChangeBehavior>, FieldBehaviorForFrontend, FieldFilterBehavior<CaseChangeBehavior>
|
||||
{
|
||||
NONE(null),
|
||||
TO_UPPER_CASE((String s) -> s.toUpperCase()),
|
||||
TO_LOWER_CASE((String s) -> s.toLowerCase());
|
||||
|
||||
|
||||
private final Function<String, String> function;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
CaseChangeBehavior(Function<String, String> function)
|
||||
{
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public CaseChangeBehavior 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 TO_UPPER_CASE, TO_LOWER_CASE -> 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 CaseChange was a applied to a non-String-like field:" + errorSuffix);
|
||||
}
|
||||
}
|
||||
|
||||
return (errors);
|
||||
}
|
||||
|
||||
}
|
@ -49,7 +49,6 @@ public interface DisplayFormat
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:Indentation")
|
||||
static String getExcelFormat(String javaDisplayFormat)
|
||||
{
|
||||
if(javaDisplayFormat == null)
|
||||
@ -58,21 +57,21 @@ public interface DisplayFormat
|
||||
}
|
||||
|
||||
return switch(javaDisplayFormat)
|
||||
{
|
||||
case DisplayFormat.DEFAULT -> null;
|
||||
case DisplayFormat.COMMAS -> "#,##0";
|
||||
case DisplayFormat.DECIMAL1 -> "0.0";
|
||||
case DisplayFormat.DECIMAL2 -> "0.00";
|
||||
case DisplayFormat.DECIMAL3 -> "0.000";
|
||||
case DisplayFormat.DECIMAL1_COMMAS -> "#,##0.0";
|
||||
case DisplayFormat.DECIMAL2_COMMAS -> "#,##0.00";
|
||||
case DisplayFormat.DECIMAL3_COMMAS -> "#,##0.000";
|
||||
case DisplayFormat.CURRENCY -> "$#,##0.00";
|
||||
case DisplayFormat.PERCENT -> "0%";
|
||||
case DisplayFormat.PERCENT_POINT1 -> "0.0%";
|
||||
case DisplayFormat.PERCENT_POINT2 -> "0.00%";
|
||||
default -> null;
|
||||
};
|
||||
{
|
||||
case DisplayFormat.DEFAULT -> null;
|
||||
case DisplayFormat.COMMAS -> "#,##0";
|
||||
case DisplayFormat.DECIMAL1 -> "0.0";
|
||||
case DisplayFormat.DECIMAL2 -> "0.00";
|
||||
case DisplayFormat.DECIMAL3 -> "0.000";
|
||||
case DisplayFormat.DECIMAL1_COMMAS -> "#,##0.0";
|
||||
case DisplayFormat.DECIMAL2_COMMAS -> "#,##0.00";
|
||||
case DisplayFormat.DECIMAL3_COMMAS -> "#,##0.000";
|
||||
case DisplayFormat.CURRENCY -> "$#,##0.00";
|
||||
case DisplayFormat.PERCENT -> "0%";
|
||||
case DisplayFormat.PERCENT_POINT1 -> "0.0%";
|
||||
case DisplayFormat.PERCENT_POINT2 -> "0.00%";
|
||||
default -> null;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Marker interface for a field behavior which you might want to send to a
|
||||
** frontend (e.g., so it can edit values to match what'll happen in the backend).
|
||||
*******************************************************************************/
|
||||
public interface FieldBehaviorForFrontend extends Serializable
|
||||
{
|
||||
}
|
@ -23,7 +23,8 @@ package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Interface to mark a field behavior as one to be used during generating
|
||||
** display values.
|
||||
*******************************************************************************/
|
||||
public interface FieldDisplayBehavior<T extends FieldDisplayBehavior<T>> extends FieldBehavior<T>
|
||||
{
|
||||
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface to mark a field behavior as one to be used before a query filter
|
||||
** is executed.
|
||||
*******************************************************************************/
|
||||
public interface FieldFilterBehavior<T extends FieldFilterBehavior<T>> extends FieldBehavior<T>
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Apply the filter to a value from a criteria.
|
||||
** If you don't want to change the input value, return the parameter.
|
||||
*******************************************************************************/
|
||||
Serializable applyToFilterCriteriaValue(Serializable value, QInstance instance, QTableMetaData table, QFieldMetaData field);
|
||||
|
||||
}
|
@ -23,13 +23,17 @@ package com.kingsrook.qqq.backend.core.model.metadata.frontend;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
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.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -53,6 +57,8 @@ public class QFrontendFieldMetaData
|
||||
private List<FieldAdornment> adornments;
|
||||
private List<QHelpContent> helpContents;
|
||||
|
||||
private List<FieldBehaviorForFrontend> behaviors;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// do not add setters. take values from the source-object in the constructor!! //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
@ -75,6 +81,18 @@ public class QFrontendFieldMetaData
|
||||
this.adornments = fieldMetaData.getAdornments();
|
||||
this.defaultValue = fieldMetaData.getDefaultValue();
|
||||
this.helpContents = fieldMetaData.getHelpContents();
|
||||
|
||||
for(FieldBehavior<?> behavior : CollectionUtils.nonNullCollection(fieldMetaData.getBehaviors()))
|
||||
{
|
||||
if(behavior instanceof FieldBehaviorForFrontend fbff)
|
||||
{
|
||||
if(behaviors == null)
|
||||
{
|
||||
behaviors = new ArrayList<>();
|
||||
}
|
||||
behaviors.add(fbff);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -198,4 +216,14 @@ public class QFrontendFieldMetaData
|
||||
return helpContents;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fieldBehaviors
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<FieldBehaviorForFrontend> getBehaviors()
|
||||
{
|
||||
return behaviors;
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
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.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.sharing.ShareableTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
|
||||
@ -76,7 +77,8 @@ public class QFrontendTableMetaData
|
||||
private boolean usesVariants;
|
||||
private String variantTableLabel;
|
||||
|
||||
private ShareableTableMetaData shareableTableMetaData;
|
||||
private ShareableTableMetaData shareableTableMetaData;
|
||||
private Map<String, List<QHelpContent>> helpContents;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// do not add setters. take values from the source-object in the constructor!! //
|
||||
@ -172,6 +174,8 @@ public class QFrontendTableMetaData
|
||||
usesVariants = true;
|
||||
variantTableLabel = actionInput.getInstance().getTable(backend.getVariantOptionsTableName()).getLabel();
|
||||
}
|
||||
|
||||
this.helpContents = tableMetaData.getHelpContent();
|
||||
}
|
||||
|
||||
|
||||
@ -382,4 +386,15 @@ public class QFrontendTableMetaData
|
||||
{
|
||||
return shareableTableMetaData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for helpContents
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, List<QHelpContent>> getHelpContents()
|
||||
{
|
||||
return helpContents;
|
||||
}
|
||||
}
|
||||
|
@ -44,14 +44,13 @@ public enum JoinType
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:indentation")
|
||||
public JoinType flip()
|
||||
{
|
||||
return switch(this)
|
||||
{
|
||||
case ONE_TO_MANY -> MANY_TO_ONE;
|
||||
case MANY_TO_ONE -> ONE_TO_MANY;
|
||||
case MANY_TO_MANY, ONE_TO_ONE -> this;
|
||||
};
|
||||
{
|
||||
case ONE_TO_MANY -> MANY_TO_ONE;
|
||||
case MANY_TO_ONE -> ONE_TO_MANY;
|
||||
case MANY_TO_MANY, ONE_TO_ONE -> this;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -60,6 +60,8 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
private List<QStepMetaData> stepList; // these are the steps that are ran, by-default, in the order they are ran in
|
||||
private Map<String, QStepMetaData> steps; // this is the full map of possible steps
|
||||
|
||||
private QBackendStepMetaData cancelStep;
|
||||
|
||||
private QIcon icon;
|
||||
|
||||
private QScheduleMetaData schedule;
|
||||
@ -675,6 +677,7 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for variantRunStrategy
|
||||
*******************************************************************************/
|
||||
@ -746,4 +749,35 @@ public class QProcessMetaData implements QAppChildMetaData, MetaDataWithPermissi
|
||||
return steps;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for cancelStep
|
||||
*******************************************************************************/
|
||||
public QBackendStepMetaData getCancelStep()
|
||||
{
|
||||
return (this.cancelStep);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for cancelStep
|
||||
*******************************************************************************/
|
||||
public void setCancelStep(QBackendStepMetaData cancelStep)
|
||||
{
|
||||
this.cancelStep = cancelStep;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for cancelStep
|
||||
*******************************************************************************/
|
||||
public QProcessMetaData withCancelStep(QBackendStepMetaData cancelStep)
|
||||
{
|
||||
this.cancelStep = cancelStep;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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.queues;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** settings that can be applied to either an SQSQueue or an SQSQueueProvider,
|
||||
** to control what the SQSQueuePoller does when it receives from AWS.
|
||||
*******************************************************************************/
|
||||
public class SQSPollerSettings
|
||||
{
|
||||
private Integer maxNumberOfMessages;
|
||||
private Integer waitTimeSeconds;
|
||||
private Integer maxLoops;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for maxNumberOfMessages
|
||||
*******************************************************************************/
|
||||
public Integer getMaxNumberOfMessages()
|
||||
{
|
||||
return (this.maxNumberOfMessages);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for maxNumberOfMessages
|
||||
*******************************************************************************/
|
||||
public void setMaxNumberOfMessages(Integer maxNumberOfMessages)
|
||||
{
|
||||
this.maxNumberOfMessages = maxNumberOfMessages;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for maxNumberOfMessages
|
||||
*******************************************************************************/
|
||||
public SQSPollerSettings withMaxNumberOfMessages(Integer maxNumberOfMessages)
|
||||
{
|
||||
this.maxNumberOfMessages = maxNumberOfMessages;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for waitTimeSeconds
|
||||
*******************************************************************************/
|
||||
public Integer getWaitTimeSeconds()
|
||||
{
|
||||
return (this.waitTimeSeconds);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for waitTimeSeconds
|
||||
*******************************************************************************/
|
||||
public void setWaitTimeSeconds(Integer waitTimeSeconds)
|
||||
{
|
||||
this.waitTimeSeconds = waitTimeSeconds;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for waitTimeSeconds
|
||||
*******************************************************************************/
|
||||
public SQSPollerSettings withWaitTimeSeconds(Integer waitTimeSeconds)
|
||||
{
|
||||
this.waitTimeSeconds = waitTimeSeconds;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for maxLoops
|
||||
*******************************************************************************/
|
||||
public Integer getMaxLoops()
|
||||
{
|
||||
return (this.maxLoops);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for maxLoops
|
||||
*******************************************************************************/
|
||||
public void setMaxLoops(Integer maxLoops)
|
||||
{
|
||||
this.maxLoops = maxLoops;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for maxLoops
|
||||
*******************************************************************************/
|
||||
public SQSPollerSettings withMaxLoops(Integer maxLoops)
|
||||
{
|
||||
this.maxLoops = maxLoops;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.queues;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** SQS subclass of meta-data for a specific Queue
|
||||
*******************************************************************************/
|
||||
public class SQSQueueMetaData extends QQueueMetaData
|
||||
{
|
||||
private SQSPollerSettings pollerSettings;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for pollerSettings
|
||||
*******************************************************************************/
|
||||
public SQSPollerSettings getPollerSettings()
|
||||
{
|
||||
return (this.pollerSettings);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for pollerSettings
|
||||
*******************************************************************************/
|
||||
public void setPollerSettings(SQSPollerSettings pollerSettings)
|
||||
{
|
||||
this.pollerSettings = pollerSettings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for pollerSettings
|
||||
*******************************************************************************/
|
||||
public SQSQueueMetaData withPollerSettings(SQSPollerSettings pollerSettings)
|
||||
{
|
||||
this.pollerSettings = pollerSettings;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -36,6 +36,8 @@ public class SQSQueueProviderMetaData extends QQueueProviderMetaData
|
||||
private String region;
|
||||
private String baseURL;
|
||||
|
||||
private SQSPollerSettings pollerSettings;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -196,4 +198,35 @@ public class SQSQueueProviderMetaData extends QQueueProviderMetaData
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for pollerSettings
|
||||
*******************************************************************************/
|
||||
public SQSPollerSettings getPollerSettings()
|
||||
{
|
||||
return (this.pollerSettings);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for pollerSettings
|
||||
*******************************************************************************/
|
||||
public void setPollerSettings(SQSPollerSettings pollerSettings)
|
||||
{
|
||||
this.pollerSettings = pollerSettings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for pollerSettings
|
||||
*******************************************************************************/
|
||||
public SQSQueueProviderMetaData withPollerSettings(SQSPollerSettings pollerSettings)
|
||||
{
|
||||
this.pollerSettings = pollerSettings;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -39,18 +39,21 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
*******************************************************************************/
|
||||
public class QStepMetaDataDeserializer extends JsonDeserializer<QStepMetaData>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
@SuppressWarnings("checkstyle:Indentation")
|
||||
public QStepMetaData deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JacksonException
|
||||
{
|
||||
TreeNode treeNode = jsonParser.readValueAsTree();
|
||||
String stepType = DeserializerUtils.readTextValue(treeNode, "stepType");
|
||||
Class<? extends QStepMetaData> targetClass = switch(stepType)
|
||||
{
|
||||
case "backend" -> QBackendStepMetaData.class;
|
||||
case "frontend" -> QFrontendStepMetaData.class;
|
||||
default -> throw new IllegalArgumentException("Unsupported StepType " + stepType + " for deserialization");
|
||||
};
|
||||
{
|
||||
case "backend" -> QBackendStepMetaData.class;
|
||||
case "frontend" -> QFrontendStepMetaData.class;
|
||||
default -> throw new IllegalArgumentException("Unsupported StepType " + stepType + " for deserialization");
|
||||
};
|
||||
return (DeserializerUtils.reflectivelyDeserialize(targetClass, treeNode));
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
|
||||
@ -43,6 +44,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
|
||||
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.help.HelpRole;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
|
||||
@ -110,6 +113,9 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
|
||||
private ShareableTableMetaData shareableTableMetaData;
|
||||
|
||||
protected Map<String, List<QHelpContent>> helpContent;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default constructor.
|
||||
@ -158,11 +164,26 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
public QTableMetaData withFieldsFromEntity(Class<? extends QRecordEntity> entityClass) throws QException
|
||||
{
|
||||
List<QRecordEntityField> recordEntityFieldList = QRecordEntity.getFieldList(entityClass);
|
||||
|
||||
boolean setPrimaryKey = false;
|
||||
|
||||
for(QRecordEntityField recordEntityField : recordEntityFieldList)
|
||||
{
|
||||
QFieldMetaData field = new QFieldMetaData(recordEntityField.getGetter());
|
||||
addField(field);
|
||||
|
||||
if(recordEntityField.getFieldAnnotation().isPrimaryKey())
|
||||
{
|
||||
if(setPrimaryKey)
|
||||
{
|
||||
throw (new QException("Attempt to set more than one field as primary key (" + primaryKeyField + "," + field.getName() + ")."));
|
||||
}
|
||||
|
||||
setPrimaryKeyField(field.getName());
|
||||
setPrimaryKey = true;
|
||||
}
|
||||
}
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -624,6 +645,18 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** fluent setter for both recordLabelFormat and recordLabelFields
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withRecordLabelFormatAndFields(String format, String... fields)
|
||||
{
|
||||
setRecordLabelFormat(format);
|
||||
setRecordLabelFields(Arrays.asList(fields));
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for recordLabelFields
|
||||
**
|
||||
@ -1388,6 +1421,7 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for shareableTableMetaData
|
||||
*******************************************************************************/
|
||||
@ -1418,4 +1452,73 @@ public class QTableMetaData implements QAppChildMetaData, Serializable, MetaData
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for helpContent
|
||||
*******************************************************************************/
|
||||
public Map<String, List<QHelpContent>> getHelpContent()
|
||||
{
|
||||
return (this.helpContent);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for helpContent
|
||||
*******************************************************************************/
|
||||
public void setHelpContent(Map<String, List<QHelpContent>> helpContent)
|
||||
{
|
||||
this.helpContent = helpContent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for helpContent
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withHelpContent(Map<String, List<QHelpContent>> helpContent)
|
||||
{
|
||||
this.helpContent = helpContent;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for adding 1 helpContent (for a slot)
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withHelpContent(String slot, QHelpContent helpContent)
|
||||
{
|
||||
if(this.helpContent == null)
|
||||
{
|
||||
this.helpContent = new HashMap<>();
|
||||
}
|
||||
|
||||
List<QHelpContent> listForSlot = this.helpContent.computeIfAbsent(slot, (k) -> new ArrayList<>());
|
||||
QInstanceHelpContentManager.putHelpContentInList(helpContent, listForSlot);
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** remove a helpContent for a slot based on its set of roles
|
||||
*******************************************************************************/
|
||||
public void removeHelpContent(String slot, Set<HelpRole> roles)
|
||||
{
|
||||
if(this.helpContent == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<QHelpContent> listForSlot = this.helpContent.get(slot);
|
||||
if(listForSlot == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, listForSlot);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QAssociation;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QIgnore;
|
||||
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.fields.ValueTooLongBehavior;
|
||||
@ -77,7 +78,9 @@ public class QueryStat extends QRecordEntity
|
||||
///////////////////////////////////////////////////////////
|
||||
// non-persistent fields - used to help build the record //
|
||||
///////////////////////////////////////////////////////////
|
||||
private String tableName;
|
||||
@QIgnore
|
||||
private String tableName;
|
||||
|
||||
private Set<String> joinTableNames;
|
||||
private QQueryFilter queryFilter;
|
||||
|
||||
@ -384,6 +387,7 @@ public class QueryStat extends QRecordEntity
|
||||
/*******************************************************************************
|
||||
** Getter for queryFilter
|
||||
*******************************************************************************/
|
||||
@QIgnore
|
||||
public QQueryFilter getQueryFilter()
|
||||
{
|
||||
return (this.queryFilter);
|
||||
@ -446,6 +450,7 @@ public class QueryStat extends QRecordEntity
|
||||
/*******************************************************************************
|
||||
** Getter for joinTableNames
|
||||
*******************************************************************************/
|
||||
@QIgnore
|
||||
public Set<String> getJoinTableNames()
|
||||
{
|
||||
return (this.joinTableNames);
|
||||
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.savedreports;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.FilterAndColumnsSetupData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SavedReportsFilterAndColumnsSetupRenderer extends AbstractWidgetRenderer
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
|
||||
{
|
||||
return (new RenderWidgetOutput(new FilterAndColumnsSetupData(null, true, false, null)));
|
||||
}
|
||||
}
|
@ -73,6 +73,7 @@ public class SavedReportsMetaDataProvider
|
||||
public static final String RENDER_REPORT_PROCESS_VALUES_WIDGET = "renderReportProcessValuesWidget";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -234,8 +235,8 @@ public class SavedReportsMetaDataProvider
|
||||
.withName("reportSetupWidget")
|
||||
.withLabel("Filters and Columns")
|
||||
.withIsCard(true)
|
||||
.withType(WidgetType.REPORT_SETUP.getType())
|
||||
.withCodeReference(new QCodeReference(DefaultWidgetRenderer.class));
|
||||
.withType(WidgetType.FILTER_AND_COLUMNS_SETUP.getType())
|
||||
.withCodeReference(new QCodeReference(SavedReportsFilterAndColumnsSetupRenderer.class));
|
||||
}
|
||||
|
||||
|
||||
|
@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.common.TimeZonePossibleValueSourceMetaDataProvider;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QAssociation;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QIgnore;
|
||||
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.fields.DisplayFormat;
|
||||
@ -434,6 +435,7 @@ public class ScheduledJob extends QRecordEntity
|
||||
/*******************************************************************************
|
||||
** Getter for jobParameters - but a map of just the key=value pairs.
|
||||
*******************************************************************************/
|
||||
@QIgnore
|
||||
public Map<String, String> getJobParametersMap()
|
||||
{
|
||||
if(CollectionUtils.nullSafeIsEmpty(this.jobParameters))
|
||||
@ -469,6 +471,7 @@ public class ScheduledJob extends QRecordEntity
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for repeatSeconds
|
||||
*******************************************************************************/
|
||||
|
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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.session;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
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.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Special session, indicating that an action being executed is being done not
|
||||
** on behalf of a (human or otherwise) user - but instead, is the application/
|
||||
** system itself.
|
||||
**
|
||||
** Generally this means, escalated privileges - e.g., permission to all tables,
|
||||
** processes etc, and all security keys (e.g., all-access keys).
|
||||
*******************************************************************************/
|
||||
public class QSystemUserSession extends QSession
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QSystemUserSession.class);
|
||||
|
||||
private static List<String> allAccessKeyNames = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QSystemUserSession()
|
||||
{
|
||||
super();
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// always give system user all of the all-access keys //
|
||||
////////////////////////////////////////////////////////
|
||||
for(String allAccessKeyName : getAllAccessKeyNames())
|
||||
{
|
||||
withSecurityKeyValue(allAccessKeyName, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** System User Sessions should always have permission to all the things.
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean hasPermission(String permissionName)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private List<String> getAllAccessKeyNames()
|
||||
{
|
||||
if(allAccessKeyNames == null)
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
if(qInstance == null)
|
||||
{
|
||||
LOG.warn("QInstance was not set in context when creating a QSystemUserSession and trying to prime allAccessKeyNames... This SystemUserSession will NOT have any allAccessKeys.");
|
||||
return (Collections.emptyList());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// ideally only 1 thread would do this, but, it's cheap, so don't bother locking. //
|
||||
// and, if multiple get in, only the last one will assign to the field, so, s/b fine //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
List<String> list = new ArrayList<>();
|
||||
for(QSecurityKeyType securityKeyType : qInstance.getSecurityKeyTypes().values())
|
||||
{
|
||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
|
||||
{
|
||||
list.add(securityKeyType.getAllAccessKeyName());
|
||||
}
|
||||
}
|
||||
|
||||
LOG.info("Initialized allAccessKeyNames for SystemUserSessions as: " + list);
|
||||
allAccessKeyNames = list;
|
||||
}
|
||||
|
||||
return (allAccessKeyNames);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meant for use in tests - to explicitly null-out the allAccessKeyNames field.
|
||||
*******************************************************************************/
|
||||
static void unsetAllAccessKeyNames()
|
||||
{
|
||||
allAccessKeyNames = null;
|
||||
}
|
||||
|
||||
}
|
@ -72,6 +72,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.Auth0AuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
@ -150,10 +151,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// this is how we allow the actions within this class to work without themselves having a logged-in user. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private static QSession chickenAndEggSession = new QSession()
|
||||
{
|
||||
|
||||
};
|
||||
private static QSession chickenAndEggSession = null;
|
||||
|
||||
|
||||
|
||||
@ -163,14 +161,29 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
*******************************************************************************/
|
||||
private QSession getChickenAndEggSession()
|
||||
{
|
||||
for(String typeName : QContext.getQInstance().getSecurityKeyTypes().keySet())
|
||||
if(chickenAndEggSession == null)
|
||||
{
|
||||
QSecurityKeyType keyType = QContext.getQInstance().getSecurityKeyType(typeName);
|
||||
if(StringUtils.hasContent(keyType.getAllAccessKeyName()))
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// if the static field is null, then let's make a new session; //
|
||||
// prime it with all all-access keys; and then set it in the static field. //
|
||||
// and, if 2 threads get in here at the same time, no real harm will be done, //
|
||||
// other than creating the session twice, and whoever loses the race, that'll //
|
||||
// be the one that stays in the field //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
QSession newChickenAndEggSession = new QSession();
|
||||
|
||||
for(String typeName : QContext.getQInstance().getSecurityKeyTypes().keySet())
|
||||
{
|
||||
chickenAndEggSession = chickenAndEggSession.withSecurityKeyValue(keyType.getAllAccessKeyName(), true);
|
||||
QSecurityKeyType keyType = QContext.getQInstance().getSecurityKeyType(typeName);
|
||||
if(StringUtils.hasContent(keyType.getAllAccessKeyName()))
|
||||
{
|
||||
newChickenAndEggSession.withSecurityKeyValue(keyType.getAllAccessKeyName(), true);
|
||||
}
|
||||
}
|
||||
|
||||
chickenAndEggSession = newChickenAndEggSession;
|
||||
}
|
||||
|
||||
return (chickenAndEggSession);
|
||||
}
|
||||
|
||||
@ -196,7 +209,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
// process a sessionUUID - looks up userSession record - cannot create token this way. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
String sessionUUID = context.get(SESSION_UUID_KEY);
|
||||
LOG.debug("Creating session from sessionUUID (userSession)", logPair("sessionUUID", maskForLog(sessionUUID)));
|
||||
LOG.trace("Creating session from sessionUUID (userSession)", logPair("sessionUUID", maskForLog(sessionUUID)));
|
||||
if(sessionUUID != null)
|
||||
{
|
||||
accessToken = getAccessTokenFromSessionUUID(metaData, sessionUUID);
|
||||
@ -254,7 +267,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
// decode the credentials from the header auth //
|
||||
/////////////////////////////////////////////////
|
||||
String base64Credentials = context.get(BASIC_AUTH_KEY).trim();
|
||||
LOG.info("Creating session from basicAuthentication", logPair("base64Credentials", maskForLog(base64Credentials)));
|
||||
LOG.trace("Creating session from basicAuthentication", logPair("base64Credentials", maskForLog(base64Credentials)));
|
||||
accessToken = getAccessTokenFromBase64BasicAuthCredentials(metaData, auth, base64Credentials);
|
||||
}
|
||||
catch(Auth0Exception e)
|
||||
@ -273,7 +286,7 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
// process an api key - looks up client application token (creating token if needed) //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
String apiKey = context.get(API_KEY);
|
||||
LOG.info("Creating session from apiKey (accessTokenTable)", logPair("apiKey", maskForLog(apiKey)));
|
||||
LOG.trace("Creating session from apiKey (accessTokenTable)", logPair("apiKey", maskForLog(apiKey)));
|
||||
if(apiKey != null)
|
||||
{
|
||||
accessToken = getAccessTokenFromApiKey(metaData, apiKey);
|
||||
@ -479,6 +492,11 @@ public class Auth0AuthenticationModule implements QAuthenticationModuleInterface
|
||||
return (true);
|
||||
}
|
||||
|
||||
if(session instanceof QSystemUserSession)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
if(session == null)
|
||||
{
|
||||
return (false);
|
||||
|
@ -51,6 +51,7 @@ 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.authentication.TableBasedAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
|
||||
@ -256,6 +257,11 @@ public class TableBasedAuthenticationModule implements QAuthenticationModuleInte
|
||||
return (true);
|
||||
}
|
||||
|
||||
if(session instanceof QSystemUserSession)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
if(session == null)
|
||||
{
|
||||
return (false);
|
||||
|
@ -769,7 +769,6 @@ public class MemoryRecordStore
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:indentation")
|
||||
private static Serializable computeAggregate(List<QRecord> records, Aggregate aggregate, QTableMetaData table)
|
||||
{
|
||||
String fieldName = aggregate.getFieldName();
|
||||
|
@ -38,7 +38,6 @@ public class MockCountAction implements CountInterface
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:MagicNumber")
|
||||
public CountOutput execute(CountInput countInput) throws QException
|
||||
{
|
||||
try
|
||||
|
@ -95,10 +95,8 @@ public class MockQueryAction implements QueryInterface
|
||||
** Get a mock value to use, based on its type.
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:MagicNumber")
|
||||
public static Serializable getMockValue(QTableMetaData table, String field)
|
||||
{
|
||||
// @formatter:off // IJ can't do new-style switch correctly yet...
|
||||
return switch(table.getField(field).getType())
|
||||
{
|
||||
case STRING -> UUID.randomUUID().toString();
|
||||
@ -112,7 +110,6 @@ public class MockQueryAction implements QueryInterface
|
||||
case PASSWORD -> "abc***234";
|
||||
default -> throw new IllegalStateException("Unexpected value: " + table.getField(field).getType());
|
||||
};
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user