mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-20 14:10:44 +00:00
Compare commits
282 Commits
version-0.
...
version-0.
Author | SHA1 | Date | |
---|---|---|---|
72a9f68de7 | |||
c9fd7c087a | |||
19d6739723 | |||
cc881e3168 | |||
2f0ef03eec | |||
4a65efa5ac | |||
6ebfbc6984 | |||
1f931ddec6 | |||
fc6a72eb09 | |||
22d0c5c79b | |||
07e575a7d7 | |||
a6e02ea1bf | |||
bf5273fa4d | |||
6bc160a213 | |||
a159bc4076 | |||
514b70eb88 | |||
c1c8528dcb | |||
d760831431 | |||
726fad0e5e | |||
d4181cc157 | |||
ca9834204b | |||
1b4c754314 | |||
905c2d1296 | |||
cbf84fd76b | |||
106976a060 | |||
8f6e4144d2 | |||
d009169770 | |||
5453e2e081 | |||
51b2a9d924 | |||
a408e4c0d2 | |||
8af83f9286 | |||
b5c5fb7dd8 | |||
12a92c6330 | |||
0268dad395 | |||
74cd0e0a57 | |||
e779c392bb | |||
e35761e0a6 | |||
cb5f3ba188 | |||
b3b7c0b381 | |||
f4b8f5c782 | |||
ffe8da448b | |||
b021aebabb | |||
f79bf85c14 | |||
6fbfbe9db2 | |||
084630918f | |||
21e3cdd0a5 | |||
5babdd11b6 | |||
7e368c6ff9 | |||
3df4513cd1 | |||
adcddff440 | |||
a9e793dfb8 | |||
f6f2cb7070 | |||
944a93bd99 | |||
7e6a09fc21 | |||
f15f63b021 | |||
037d67dd38 | |||
b0d0c0ce6c | |||
4a87856601 | |||
07aa9cfa27 | |||
ef6ccc61c3 | |||
e62d2332ac | |||
0eff8d7d03 | |||
a64a2801c0 | |||
a43660a05a | |||
df259b5f82 | |||
7034671070 | |||
6629015fbf | |||
e9c66b48f2 | |||
ba805a4c92 | |||
17d4c81cc3 | |||
74cf24a00e | |||
1d2acc7364 | |||
11977624bf | |||
c369430261 | |||
94d32fa854 | |||
4da3cc9206 | |||
8ea571ccf7 | |||
924a6ba31f | |||
3f7f2b58e2 | |||
fd167a7c64 | |||
f311c7af88 | |||
af425bd567 | |||
c6fa22524c | |||
303cd4aec0 | |||
90a7745246 | |||
4a29898405 | |||
4485898d0e | |||
8924343490 | |||
f13ee0d1ca | |||
d0e8bd9db2 | |||
d6a9c8f0e0 | |||
8cfa2736da | |||
9825893f9b | |||
e04d50674b | |||
c9e86896e5 | |||
b16eaca394 | |||
939dcc308c | |||
bf44f97630 | |||
9391284479 | |||
ad2eff0e73 | |||
bbde64b02d | |||
001ec3a34a | |||
dd28c95fc0 | |||
da17145f66 | |||
03e9f27866 | |||
248db43c8f | |||
9cbb899788 | |||
d569541b77 | |||
21500b642f | |||
fb6cef66ef | |||
4d7c7f48be | |||
0e01372200 | |||
b6e089a364 | |||
61286cf013 | |||
7150886e39 | |||
7ca9ecbcec | |||
1429d1000c | |||
54d3e4a6c8 | |||
b395ee6778 | |||
ffc574e83f | |||
e0f5c3ff49 | |||
ec05f7ab7e | |||
33f4c1235a | |||
ad8ed4c574 | |||
bfe138c018 | |||
054c34918d | |||
7956c8f455 | |||
3a172b3fb4 | |||
e53a982d12 | |||
02c51ae9ab | |||
eae164c686 | |||
10afa1a80e | |||
2164c2115d | |||
00baf64587 | |||
5368903723 | |||
81a8f100b9 | |||
7e87950ef5 | |||
d7abab2fd1 | |||
55fa105797 | |||
259329e9aa | |||
46baceee31 | |||
8131dcc644 | |||
f4ea645055 | |||
f454e0aefa | |||
11a16590ef | |||
5ca3c088a6 | |||
1212b47926 | |||
9526c0b59f | |||
1ebe43fe6f | |||
2162a6832b | |||
d0fb8c33e9 | |||
eafa82eb85 | |||
47d2291d96 | |||
68686c0e17 | |||
22644b6a36 | |||
c640561f53 | |||
c091440848 | |||
0a2d1d66da | |||
60056caa2b | |||
0c9d6ba912 | |||
17e9a91c86 | |||
f0fd0d3fe6 | |||
d41e92d213 | |||
4f3c03de1a | |||
ea731bac5c | |||
8a8f0d6e6f | |||
1baf7d8f86 | |||
4db174b66d | |||
5074ed1867 | |||
9be0eb9e76 | |||
94a970b8e8 | |||
80eee299d7 | |||
b5aa8e8152 | |||
ea795ed701 | |||
8440536d35 | |||
7ea1750800 | |||
4cf8e37e7e | |||
1bdce12b8d | |||
f28af62c5e | |||
63be3f01a7 | |||
f3cf327384 | |||
d5cb752132 | |||
8833563d26 | |||
5a6a0e2ac5 | |||
395f18f513 | |||
67244d6c6e | |||
05a7f9d847 | |||
049c60c6e3 | |||
9659e5b9ea | |||
0e4fef561e | |||
ac074b8492 | |||
078536a020 | |||
97b22774f1 | |||
194833bf07 | |||
8924657fc1 | |||
3071c63857 | |||
c07237b7d2 | |||
f01a1ac7a1 | |||
7e07fd04a1 | |||
ff6c2b7fa6 | |||
d9a17ac99b | |||
81f9f4e49a | |||
927e7f725a | |||
07e6c7019d | |||
eae01bb8c4 | |||
2d09a521cd | |||
92f6f7da04 | |||
c863027629 | |||
b0cca3f1d7 | |||
e3c4a3d91d | |||
0dcc5ef8c5 | |||
f1515e2ba4 | |||
c620917606 | |||
700802f329 | |||
f6220482cd | |||
1e1ecbccee | |||
1863c31907 | |||
95040d06b8 | |||
dd78c7e51f | |||
84e2a7e718 | |||
668bf5e622 | |||
27b48b62f9 | |||
583d716f94 | |||
142bd70212 | |||
13a7281d3a | |||
f61a3a13be | |||
3e56bb4ecd | |||
789a1c8e63 | |||
81d8b66c0c | |||
a7faf52817 | |||
45c703a6df | |||
5a9a0754a6 | |||
4790f55243 | |||
f0450ef621 | |||
ed5839aa0a | |||
56f05c74fc | |||
4f8f8bad9a | |||
4a1853faa5 | |||
c972f9cecc | |||
efa796bb39 | |||
a05e74a7d0 | |||
41fcb09c70 | |||
6b12390f6c | |||
9889320fa6 | |||
c00120a1fc | |||
029f436071 | |||
f1ff53059f | |||
18a8656ba2 | |||
e75b645639 | |||
804efbd608 | |||
fe30d2654b | |||
97b803a86a | |||
2daa42aeb1 | |||
4344fa472a | |||
6bb810c1f4 | |||
d65af53090 | |||
ac6a7ba15a | |||
1c150e207a | |||
f9408716ac | |||
396f02265b | |||
a1674792c6 | |||
22565b3ecd | |||
d2e7b794f4 | |||
7000da409a | |||
3f0e09e32a | |||
70d9d259c1 | |||
4dc9c52ee0 | |||
964405d210 | |||
369ba3c8d7 | |||
24d5406ee3 | |||
984012f3a3 | |||
e8264d915f | |||
46adfa8e24 | |||
178078282c | |||
d03e947889 | |||
43de1cf749 | |||
d3fa1df56f | |||
69a6104393 | |||
e831c75e1e | |||
eba9b0b4af | |||
9f82f35bcf | |||
680696491f |
@ -1,7 +1,6 @@
|
||||
version: 2.1
|
||||
|
||||
orbs:
|
||||
slack: circleci/slack@4.10.1
|
||||
localstack: localstack/platform@1.0
|
||||
|
||||
commands:
|
||||
@ -12,8 +11,10 @@ commands:
|
||||
steps:
|
||||
- store_artifacts:
|
||||
path: << parameters.module >>/target/site/jacoco/index.html
|
||||
when: always
|
||||
- store_artifacts:
|
||||
path: << parameters.module >>/target/site/jacoco/jacoco-resources
|
||||
when: always
|
||||
|
||||
install_java17:
|
||||
steps:
|
||||
@ -24,6 +25,11 @@ commands:
|
||||
sudo apt install -y openjdk-17-jdk
|
||||
sudo rm /etc/alternatives/java
|
||||
sudo ln -s /usr/lib/jvm/java-17-openjdk-amd64/bin/java /etc/alternatives/java
|
||||
- run:
|
||||
name: Install html2text
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y html2text
|
||||
|
||||
mvn_verify:
|
||||
steps:
|
||||
@ -59,6 +65,11 @@ commands:
|
||||
when: always
|
||||
- store_test_results:
|
||||
path: ~/test-results
|
||||
- run:
|
||||
name: Find Un-tested Classes
|
||||
command: |
|
||||
set +o pipefail && for i in */target/site/jacoco/*/index.html; do html2text -width 500 -nobs $i | sed '1,/^Total/d;' | grep -v Created | sed 's/ \+/ /g' | sed 's/ [[:digit:]]$//' | grep -v 0$ | cut -d' ' -f1; done
|
||||
when: always
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.m2
|
||||
@ -86,8 +97,6 @@ jobs:
|
||||
- localstack/startup
|
||||
- install_java17
|
||||
- mvn_verify
|
||||
- slack/notify:
|
||||
event: fail
|
||||
|
||||
mvn_deploy:
|
||||
executor: localstack/default
|
||||
@ -96,14 +105,12 @@ jobs:
|
||||
- install_java17
|
||||
- mvn_verify
|
||||
- mvn_jar_deploy
|
||||
- slack/notify:
|
||||
event: always
|
||||
|
||||
workflows:
|
||||
test_only:
|
||||
jobs:
|
||||
- mvn_test:
|
||||
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
||||
context: [ qqq-maven-registry-credentials, build-qqq-sample-app ]
|
||||
filters:
|
||||
branches:
|
||||
ignore: /dev/
|
||||
@ -113,7 +120,7 @@ workflows:
|
||||
deploy:
|
||||
jobs:
|
||||
- mvn_deploy:
|
||||
context: [ qqq-maven-registry-credentials, kingsrook-slack, build-qqq-sample-app ]
|
||||
context: [ qqq-maven-registry-credentials, build-qqq-sample-app ]
|
||||
filters:
|
||||
branches:
|
||||
only: /dev/
|
||||
|
@ -35,3 +35,4 @@ 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/>.
|
||||
|
||||
|
5
pom.xml
5
pom.xml
@ -37,13 +37,14 @@
|
||||
<module>qqq-middleware-picocli</module>
|
||||
<module>qqq-middleware-javalin</module>
|
||||
<module>qqq-middleware-lambda</module>
|
||||
<module>qqq-middleware-slack</module>
|
||||
<module>qqq-middleware-api</module>
|
||||
<module>qqq-utility-lambdas</module>
|
||||
<module>qqq-sample-project</module>
|
||||
<module>qqq-middleware-slack</module>
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<revision>0.10.0</revision>
|
||||
<revision>0.13.0</revision>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
@ -56,20 +56,31 @@
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>quicksight</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.amazonaws</groupId>
|
||||
<artifactId>aws-java-sdk-secretsmanager</artifactId>
|
||||
<version>1.12.385</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.14.0-rc1</version>
|
||||
<version>2.14.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.13.0</version>
|
||||
<version>2.14.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||
<version>2.14.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20210307</version>
|
||||
<version>20230227</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
@ -25,6 +25,8 @@ package com.kingsrook.qqq.backend.core.actions;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||
@ -50,11 +52,13 @@ public abstract class AbstractQActionBiConsumer<I extends AbstractActionInput, O
|
||||
*******************************************************************************/
|
||||
public Future<Void> executeAsync(I input, O output)
|
||||
{
|
||||
CapturedContext capturedContext = QContext.capture();
|
||||
CompletableFuture<Void> completableFuture = new CompletableFuture<>();
|
||||
Executors.newCachedThreadPool().submit(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
QContext.init(capturedContext);
|
||||
execute(input, output);
|
||||
completableFuture.complete(null);
|
||||
}
|
||||
@ -62,6 +66,10 @@ public abstract class AbstractQActionBiConsumer<I extends AbstractActionInput, O
|
||||
{
|
||||
completableFuture.completeExceptionally(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.clear();
|
||||
}
|
||||
});
|
||||
return (completableFuture);
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ package com.kingsrook.qqq.backend.core.actions;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||
@ -50,11 +52,13 @@ public abstract class AbstractQActionFunction<I extends AbstractActionInput, O e
|
||||
*******************************************************************************/
|
||||
public Future<O> executeAsync(I input)
|
||||
{
|
||||
CapturedContext capturedContext = QContext.capture();
|
||||
CompletableFuture<O> completableFuture = new CompletableFuture<>();
|
||||
Executors.newCachedThreadPool().submit(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
QContext.init(capturedContext);
|
||||
O output = execute(input);
|
||||
completableFuture.complete(output);
|
||||
}
|
||||
@ -62,6 +66,10 @@ public abstract class AbstractQActionFunction<I extends AbstractActionInput, O e
|
||||
{
|
||||
completableFuture.completeExceptionally(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.clear();
|
||||
}
|
||||
});
|
||||
return (completableFuture);
|
||||
}
|
||||
|
@ -25,9 +25,12 @@ package com.kingsrook.qqq.backend.core.actions;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QAuthenticationException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
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;
|
||||
|
||||
@ -43,9 +46,22 @@ public class ActionHelper
|
||||
*******************************************************************************/
|
||||
public static void validateSession(AbstractActionInput request) throws QException
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
QSession qSession = QContext.getQSession();
|
||||
|
||||
if(qInstance == null)
|
||||
{
|
||||
throw (new QException("QInstance was not set in QContext."));
|
||||
}
|
||||
|
||||
if(qSession == null)
|
||||
{
|
||||
throw (new QException("QSession was not set in QContext."));
|
||||
}
|
||||
|
||||
QAuthenticationModuleDispatcher qAuthenticationModuleDispatcher = new QAuthenticationModuleDispatcher();
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(request.getAuthenticationMetaData());
|
||||
if(!authenticationModule.isSessionValid(request.getInstance(), request.getSession()))
|
||||
QAuthenticationModuleInterface authenticationModule = qAuthenticationModuleDispatcher.getQModule(qInstance.getAuthentication());
|
||||
if(!authenticationModule.isSessionValid(qInstance, qSession))
|
||||
{
|
||||
throw new QAuthenticationException("Invalid session in request");
|
||||
}
|
||||
|
@ -95,6 +95,23 @@ public class AsyncJobCallback
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Update the current and total fields, but ONLY if the new values are
|
||||
** both >= the previous values.
|
||||
*******************************************************************************/
|
||||
public void updateStatusOnlyUpwards(int current, int total)
|
||||
{
|
||||
boolean currentIsOkay = (this.asyncJobStatus.getCurrent() == null || this.asyncJobStatus.getCurrent() <= current);
|
||||
boolean totalIsOkay = (this.asyncJobStatus.getTotal() == null || this.asyncJobStatus.getTotal() <= total);
|
||||
|
||||
if(currentIsOkay && totalIsOkay)
|
||||
{
|
||||
updateStatus(current, total);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Increase the 'current' value in the '1 of 2' sense.
|
||||
*******************************************************************************/
|
||||
|
@ -30,13 +30,15 @@ import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import com.kingsrook.qqq.backend.core.context.CapturedContext;
|
||||
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.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 org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -44,7 +46,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class AsyncJobManager
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(AsyncJobManager.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(AsyncJobManager.class);
|
||||
|
||||
|
||||
|
||||
@ -72,8 +74,10 @@ public class AsyncJobManager
|
||||
|
||||
try
|
||||
{
|
||||
CapturedContext capturedContext = QContext.capture();
|
||||
CompletableFuture<T> future = CompletableFuture.supplyAsync(() ->
|
||||
{
|
||||
QContext.init(capturedContext);
|
||||
return (runAsyncJob(jobName, asyncJob, uuidAndTypeStateKey, asyncJobStatus));
|
||||
});
|
||||
|
||||
@ -147,12 +151,13 @@ public class AsyncJobManager
|
||||
asyncJobStatus.setState(AsyncJobState.ERROR);
|
||||
asyncJobStatus.setCaughtException(e);
|
||||
getStateProvider().put(uuidAndTypeStateKey, asyncJobStatus);
|
||||
LOG.warn("Job " + uuidAndTypeStateKey.getUuid() + " ended with an exception: ", e);
|
||||
LOG.warn("Job ended with an exception", e, logPair("jobId", uuidAndTypeStateKey.getUuid()));
|
||||
throw (new CompletionException(e));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Thread.currentThread().setName(originalThreadName);
|
||||
QContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,9 +27,10 @@ import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeSupplier;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -40,13 +41,14 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class AsyncRecordPipeLoop
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(AsyncRecordPipeLoop.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(AsyncRecordPipeLoop.class);
|
||||
|
||||
private static final int TIMEOUT_AFTER_NO_RECORDS_MS = 10 * 60 * 1000;
|
||||
|
||||
private static final int MAX_SLEEP_MS = 1000;
|
||||
private static final int INIT_SLEEP_MS = 10;
|
||||
private static final int MIN_RECORDS_TO_CONSUME = 10;
|
||||
private static final int MAX_SLEEP_MS = 1000;
|
||||
private static final int INIT_SLEEP_MS = 10;
|
||||
|
||||
private Integer minRecordsToConsume = 10;
|
||||
|
||||
|
||||
|
||||
@ -62,7 +64,7 @@ public class AsyncRecordPipeLoop
|
||||
** @param consumer lambda that consumes records from the pipe
|
||||
* e.g., a transform/load step.
|
||||
*******************************************************************************/
|
||||
public int run(String jobName, Integer recordLimit, RecordPipe recordPipe, UnsafeFunction<AsyncJobCallback, ? extends Serializable> supplier, UnsafeSupplier<Integer> consumer) throws QException
|
||||
public int run(String jobName, Integer recordLimit, RecordPipe recordPipe, UnsafeFunction<AsyncJobCallback, ? extends Serializable, QException> supplier, UnsafeSupplier<Integer, QException> consumer) throws QException
|
||||
{
|
||||
///////////////////////////////////////////////////
|
||||
// start the extraction function as an async job //
|
||||
@ -82,7 +84,7 @@ public class AsyncRecordPipeLoop
|
||||
|
||||
while(jobState.equals(AsyncJobState.RUNNING))
|
||||
{
|
||||
if(recordPipe.countAvailableRecords() < MIN_RECORDS_TO_CONSUME)
|
||||
if(recordPipe.countAvailableRecords() < minRecordsToConsume)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////
|
||||
// if the pipe is too empty, sleep to let the producer work. //
|
||||
@ -178,29 +180,32 @@ public class AsyncRecordPipeLoop
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Getter for minRecordsToConsume
|
||||
*******************************************************************************/
|
||||
@FunctionalInterface
|
||||
public interface UnsafeFunction<T, R>
|
||||
public Integer getMinRecordsToConsume()
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
R apply(T t) throws QException;
|
||||
return (this.minRecordsToConsume);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Setter for minRecordsToConsume
|
||||
*******************************************************************************/
|
||||
@FunctionalInterface
|
||||
public interface UnsafeSupplier<T>
|
||||
public void setMinRecordsToConsume(Integer minRecordsToConsume)
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
T get() throws QException;
|
||||
this.minRecordsToConsume = minRecordsToConsume;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for minRecordsToConsume
|
||||
*******************************************************************************/
|
||||
public AsyncRecordPipeLoop withMinRecordsToConsume(Integer minRecordsToConsume)
|
||||
{
|
||||
this.minRecordsToConsume = minRecordsToConsume;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,333 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.audits;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
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.audits.AuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.audits.AuditOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.audits.AuditSingleInput;
|
||||
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.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QUser;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Insert 1 or more audits (and optionally their children, auditDetails)
|
||||
**
|
||||
** Takes care of managing the foreign key tables (auditTable, auditUser).
|
||||
**
|
||||
** Enforces that security key values are provided, if the table has any. Note that
|
||||
** might mean a null is given for a particular key, but at least the key must be present.
|
||||
*******************************************************************************/
|
||||
public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput>
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(AuditAction.class);
|
||||
|
||||
private Map<Pair<String, String>, Integer> cachedFetches = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Execute to insert 1 audit, with no details (child records)
|
||||
*******************************************************************************/
|
||||
public static void execute(String tableName, Integer recordId, Map<String, Serializable> securityKeyValues, String message)
|
||||
{
|
||||
execute(tableName, recordId, securityKeyValues, message, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Execute to insert 1 audit, with a list of detail child records
|
||||
*******************************************************************************/
|
||||
public static void execute(String tableName, Integer recordId, Map<String, Serializable> securityKeyValues, String message, List<QRecord> details)
|
||||
{
|
||||
new AuditAction().execute(new AuditInput().withAuditSingleInput(new AuditSingleInput()
|
||||
.withAuditTableName(tableName)
|
||||
.withRecordId(recordId)
|
||||
.withSecurityKeyValues(securityKeyValues)
|
||||
.withMessage(message)
|
||||
.withDetails(details)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Add 1 auditSingleInput to an AuditInput object - with no details (child records).
|
||||
*******************************************************************************/
|
||||
public static AuditInput appendToInput(AuditInput auditInput, String tableName, Integer recordId, Map<String, Serializable> securityKeyValues, String message)
|
||||
{
|
||||
return (appendToInput(auditInput, tableName, recordId, securityKeyValues, message, null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Add 1 auditSingleInput to an AuditInput object - with a list of details (child records).
|
||||
*******************************************************************************/
|
||||
public static AuditInput appendToInput(AuditInput auditInput, String tableName, Integer recordId, Map<String, Serializable> securityKeyValues, String message, List<QRecord> details)
|
||||
{
|
||||
if(auditInput == null)
|
||||
{
|
||||
auditInput = new AuditInput();
|
||||
}
|
||||
|
||||
return auditInput.withAuditSingleInput(new AuditSingleInput()
|
||||
.withAuditTableName(tableName)
|
||||
.withRecordId(recordId)
|
||||
.withSecurityKeyValues(securityKeyValues)
|
||||
.withMessage(message)
|
||||
.withDetails(details)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public AuditOutput execute(AuditInput input)
|
||||
{
|
||||
AuditOutput auditOutput = new AuditOutput();
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(input.getAuditSingleInputList()))
|
||||
{
|
||||
try
|
||||
{
|
||||
List<QRecord> auditRecords = new ArrayList<>();
|
||||
|
||||
for(AuditSingleInput auditSingleInput : CollectionUtils.nonNullList(input.getAuditSingleInputList()))
|
||||
{
|
||||
/////////////////////////////////////////
|
||||
// validate table is known in instance //
|
||||
/////////////////////////////////////////
|
||||
QTableMetaData table = QContext.getQInstance().getTable(auditSingleInput.getAuditTableName());
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QException("Requested audit for an unrecognized table name: " + auditSingleInput.getAuditTableName()));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////
|
||||
// validate security keys on the table are given //
|
||||
///////////////////////////////////////////////////
|
||||
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(table.getRecordSecurityLocks()))
|
||||
{
|
||||
if(auditSingleInput.getSecurityKeyValues() == null || !auditSingleInput.getSecurityKeyValues().containsKey(recordSecurityLock.getSecurityKeyType()))
|
||||
{
|
||||
throw (new QException("Missing securityKeyValue [" + recordSecurityLock.getSecurityKeyType() + "] in audit request for table " + auditSingleInput.getAuditTableName()));
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// map names to ids and handle default values //
|
||||
////////////////////////////////////////////////
|
||||
Integer auditTableId = getIdForName("auditTable", auditSingleInput.getAuditTableName());
|
||||
Integer auditUserId = getIdForName("auditUser", Objects.requireNonNullElse(auditSingleInput.getAuditUserName(), getSessionUserName()));
|
||||
Instant timestamp = Objects.requireNonNullElse(auditSingleInput.getTimestamp(), Instant.now());
|
||||
|
||||
//////////////////
|
||||
// build record //
|
||||
//////////////////
|
||||
QRecord record = new QRecord()
|
||||
.withValue("auditTableId", auditTableId)
|
||||
.withValue("auditUserId", auditUserId)
|
||||
.withValue("timestamp", timestamp)
|
||||
.withValue("message", auditSingleInput.getMessage())
|
||||
.withValue("recordId", auditSingleInput.getRecordId());
|
||||
|
||||
if(auditSingleInput.getSecurityKeyValues() != null)
|
||||
{
|
||||
for(Map.Entry<String, Serializable> entry : auditSingleInput.getSecurityKeyValues().entrySet())
|
||||
{
|
||||
record.setValue(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
auditRecords.add(record);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// do a single bulk insert //
|
||||
/////////////////////////////
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName("audit");
|
||||
insertInput.setRecords(auditRecords);
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
|
||||
//////////////////////////////////////////
|
||||
// now look for children (auditDetails) //
|
||||
//////////////////////////////////////////
|
||||
int i = 0;
|
||||
List<QRecord> auditDetailRecords = new ArrayList<>();
|
||||
for(AuditSingleInput auditSingleInput : CollectionUtils.nonNullList(input.getAuditSingleInputList()))
|
||||
{
|
||||
Integer auditId = insertOutput.getRecords().get(i++).getValueInteger("id");
|
||||
if(auditId == null)
|
||||
{
|
||||
LOG.warn("Missing an id for inserted audit - so won't be able to store its child details...");
|
||||
continue;
|
||||
}
|
||||
|
||||
for(QRecord detail : CollectionUtils.nonNullList(auditSingleInput.getDetails()))
|
||||
{
|
||||
auditDetailRecords.add(detail.withValue("auditId", auditId));
|
||||
}
|
||||
}
|
||||
|
||||
insertInput = new InsertInput();
|
||||
insertInput.setTableName("auditDetail");
|
||||
insertInput.setRecords(auditDetailRecords);
|
||||
new InsertAction().execute(insertInput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error performing an audit", e);
|
||||
}
|
||||
}
|
||||
|
||||
return (auditOutput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static String getSessionUserName()
|
||||
{
|
||||
QUser user = QContext.getQSession().getUser();
|
||||
if(user == null)
|
||||
{
|
||||
return ("Unknown");
|
||||
}
|
||||
return (user.getFullName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Integer getIdForName(String tableName, String nameValue) throws QException
|
||||
{
|
||||
Pair<String, String> key = new Pair<>(tableName, nameValue);
|
||||
if(!cachedFetches.containsKey(key))
|
||||
{
|
||||
|
||||
Integer id = fetchIdFromName(tableName, nameValue);
|
||||
if(id != null)
|
||||
{
|
||||
cachedFetches.put(key, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LOG.debug("Inserting " + tableName + " named " + nameValue);
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(tableName);
|
||||
QRecord record = new QRecord().withValue("name", nameValue);
|
||||
|
||||
if(tableName.equals("auditTable"))
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(nameValue);
|
||||
if(table != null)
|
||||
{
|
||||
record.setValue("label", table.getLabel());
|
||||
}
|
||||
}
|
||||
|
||||
insertInput.setRecords(List.of(record));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
id = insertOutput.getRecords().get(0).getValueInteger("id");
|
||||
if(id != null)
|
||||
{
|
||||
cachedFetches.put(key, id);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// assume this may mean a dupe-key - so - try another fetch below //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
LOG.debug("Caught error inserting " + tableName + " named " + nameValue + " - will try to re-fetch", e);
|
||||
}
|
||||
|
||||
id = fetchIdFromName(tableName, nameValue);
|
||||
if(id != null)
|
||||
{
|
||||
cachedFetches.put(key, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/////////////
|
||||
// give up //
|
||||
/////////////
|
||||
throw (new QException("Unable to get id for " + tableName + " named " + nameValue));
|
||||
}
|
||||
|
||||
return (cachedFetches.get(key));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Integer fetchIdFromName(String tableName, String nameValue) throws QException
|
||||
{
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(tableName);
|
||||
getInput.setUniqueKey(Map.of("name", nameValue));
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
if(getOutput.getRecord() != null)
|
||||
{
|
||||
return (getOutput.getRecord().getValueInteger("id"));
|
||||
}
|
||||
|
||||
return (null);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,398 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.audits;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
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.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.audits.AuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.audits.DMLAuditOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||
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.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Audit for a standard DML (Data Manipulation Language) activity - e.g.,
|
||||
** insert, edit, or delete.
|
||||
*******************************************************************************/
|
||||
public class DMLAuditAction extends AbstractQActionFunction<DMLAuditInput, DMLAuditOutput>
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(DMLAuditAction.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public DMLAuditOutput execute(DMLAuditInput input) throws QException
|
||||
{
|
||||
DMLAuditOutput output = new DMLAuditOutput();
|
||||
AbstractTableActionInput tableActionInput = input.getTableActionInput();
|
||||
List<QRecord> recordList = input.getRecordList();
|
||||
List<QRecord> oldRecordList = input.getOldRecordList();
|
||||
QTableMetaData table = tableActionInput.getTable();
|
||||
long start = System.currentTimeMillis();
|
||||
DMLType dmlType = getDMLType(tableActionInput);
|
||||
|
||||
try
|
||||
{
|
||||
AuditLevel auditLevel = getAuditLevel(tableActionInput);
|
||||
if(auditLevel == null || auditLevel.equals(AuditLevel.NONE) || CollectionUtils.nullSafeIsEmpty(recordList))
|
||||
{
|
||||
/////////////////////////////////////////////
|
||||
// return with noop for null or level NONE //
|
||||
/////////////////////////////////////////////
|
||||
return (output);
|
||||
}
|
||||
|
||||
String contextSuffix = "";
|
||||
Optional<AbstractActionInput> actionInput = QContext.getFirstActionInStack();
|
||||
if(actionInput.isPresent() && actionInput.get() instanceof RunProcessInput runProcessInput)
|
||||
{
|
||||
String processName = runProcessInput.getProcessName();
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
if(process != null)
|
||||
{
|
||||
contextSuffix = " during process: " + process.getLabel();
|
||||
}
|
||||
}
|
||||
|
||||
QSession qSession = QContext.getQSession();
|
||||
String apiVersion = qSession.getValue("apiVersion");
|
||||
if(apiVersion != null)
|
||||
{
|
||||
String apiLabel = qSession.getValue("apiLabel");
|
||||
if(!StringUtils.hasContent(apiLabel))
|
||||
{
|
||||
apiLabel = "API";
|
||||
}
|
||||
contextSuffix += (" via " + apiLabel + " Version: " + apiVersion);
|
||||
}
|
||||
|
||||
AuditInput auditInput = new AuditInput();
|
||||
if(auditLevel.equals(AuditLevel.RECORD) || (auditLevel.equals(AuditLevel.FIELD) && !dmlType.supportsFields))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// make many simple audits (no details) for RECORD level //
|
||||
// or for FIELD level, but on a DML type that doesn't support field-level details (e.g., DELETE or OTHER) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QRecord record : recordList)
|
||||
{
|
||||
AuditAction.appendToInput(auditInput, table.getName(), record.getValueInteger(table.getPrimaryKeyField()), getRecordSecurityKeyValues(table, record), "Record was " + dmlType.pastTenseVerb + contextSuffix);
|
||||
}
|
||||
}
|
||||
else if(auditLevel.equals(AuditLevel.FIELD))
|
||||
{
|
||||
Map<Serializable, QRecord> oldRecordMap = buildOldRecordMap(table, oldRecordList);
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
// do many audits, all with field level details, for FIELD level //
|
||||
///////////////////////////////////////////////////////////////////
|
||||
QPossibleValueTranslator qPossibleValueTranslator = new QPossibleValueTranslator(QContext.getQInstance(), qSession);
|
||||
qPossibleValueTranslator.translatePossibleValuesInRecords(table, CollectionUtils.mergeLists(recordList, oldRecordList));
|
||||
|
||||
//////////////////////////////////////////
|
||||
// sort the field names by their labels //
|
||||
//////////////////////////////////////////
|
||||
List<String> sortedFieldNames = table.getFields().keySet().stream()
|
||||
.sorted(Comparator.comparing(fieldName -> table.getFields().get(fieldName).getLabel()))
|
||||
.toList();
|
||||
|
||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
|
||||
//////////////////////////////////////////////
|
||||
// build single audit input for each record //
|
||||
//////////////////////////////////////////////
|
||||
for(QRecord record : recordList)
|
||||
{
|
||||
QRecord oldRecord = oldRecordMap.get(ValueUtils.getValueAsFieldType(primaryKeyField.getType(), record.getValue(primaryKeyField.getName())));
|
||||
|
||||
List<QRecord> details = new ArrayList<>();
|
||||
for(String fieldName : sortedFieldNames)
|
||||
{
|
||||
if(!record.getValues().containsKey(fieldName))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the stored record doesn't have this field name, then don't audit anything about it //
|
||||
// this is to deal with our Patch style updates not looking like every field was cleared out. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
continue;
|
||||
}
|
||||
|
||||
if(fieldName.equals("modifyDate") || fieldName.equals("createDate") || fieldName.equals("automationStatus"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QFieldMetaData field = table.getField(fieldName);
|
||||
Serializable value = ValueUtils.getValueAsFieldType(field.getType(), record.getValue(fieldName));
|
||||
Serializable oldValue = oldRecord == null ? null : ValueUtils.getValueAsFieldType(field.getType(), oldRecord.getValue(fieldName));
|
||||
QRecord detailRecord = null;
|
||||
|
||||
if(oldRecord == null)
|
||||
{
|
||||
if(DMLType.INSERT.equals(dmlType) && value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
String formattedValue = getFormattedValueForAuditDetail(record, fieldName, field, value);
|
||||
detailRecord = new QRecord().withValue("message", "Set " + field.getLabel() + " to " + formattedValue);
|
||||
detailRecord.withValue("newValue", formattedValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!Objects.equals(oldValue, value))
|
||||
{
|
||||
String formattedValue = getFormattedValueForAuditDetail(record, fieldName, field, value);
|
||||
String formattedOldValue = getFormattedValueForAuditDetail(oldRecord, fieldName, field, oldValue);
|
||||
|
||||
if(oldValue == null)
|
||||
{
|
||||
detailRecord = new QRecord().withValue("message", "Set " + field.getLabel() + " to " + formatFormattedValueForDetailMessage(field, formattedValue));
|
||||
detailRecord.withValue("newValue", formattedValue);
|
||||
}
|
||||
else if(value == null)
|
||||
{
|
||||
detailRecord = new QRecord().withValue("message", "Removed " + formatFormattedValueForDetailMessage(field, formattedOldValue) + " from " + field.getLabel());
|
||||
detailRecord.withValue("oldValue", formattedOldValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
detailRecord = new QRecord().withValue("message", "Changed " + field.getLabel() + " from " + formatFormattedValueForDetailMessage(field, formattedOldValue) + " to " + formatFormattedValueForDetailMessage(field, formattedValue));
|
||||
detailRecord.withValue("oldValue", formattedOldValue);
|
||||
detailRecord.withValue("newValue", formattedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(detailRecord != null)
|
||||
{
|
||||
detailRecord.withValue("fieldName", fieldName);
|
||||
details.add(detailRecord);
|
||||
}
|
||||
}
|
||||
|
||||
if(details.isEmpty() && DMLType.UPDATE.equals(dmlType))
|
||||
{
|
||||
// no, let's just noop.
|
||||
// details.add(new QRecord().withValue("message", "No fields values were changed."));
|
||||
}
|
||||
else
|
||||
{
|
||||
AuditAction.appendToInput(auditInput, table.getName(), record.getValueInteger(table.getPrimaryKeyField()), getRecordSecurityKeyValues(table, record), "Record was " + dmlType.pastTenseVerb + contextSuffix, details);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// new AuditAction().executeAsync(auditInput); // todo async??? maybe get that from rules???
|
||||
new AuditAction().execute(auditInput);
|
||||
long end = System.currentTimeMillis();
|
||||
LOG.debug("Audit performance", logPair("auditLevel", String.valueOf(auditLevel)), logPair("recordCount", recordList.size()), logPair("millis", (end - start)));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error performing DML audit", e, logPair("type", String.valueOf(dmlType)), logPair("table", table.getName()));
|
||||
}
|
||||
|
||||
return (output);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static String getFormattedValueForAuditDetail(QRecord record, String fieldName, QFieldMetaData field, Serializable value)
|
||||
{
|
||||
String formattedValue = null;
|
||||
if(value != null)
|
||||
{
|
||||
if(field.getType().equals(QFieldType.DATE_TIME) && value instanceof Instant instant)
|
||||
{
|
||||
formattedValue = QValueFormatter.formatDateTimeWithZone(instant.atZone(ZoneId.of(Objects.requireNonNullElse(QContext.getQInstance().getDefaultTimeZoneId(), "UTC"))));
|
||||
}
|
||||
else if(record.getDisplayValue(fieldName) != null)
|
||||
{
|
||||
formattedValue = record.getDisplayValue(fieldName);
|
||||
}
|
||||
else
|
||||
{
|
||||
formattedValue = QValueFormatter.formatValue(field, value);
|
||||
}
|
||||
}
|
||||
|
||||
return formattedValue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static String formatFormattedValueForDetailMessage(QFieldMetaData field, String formattedValue)
|
||||
{
|
||||
if(formattedValue == null || "null".equals(formattedValue))
|
||||
{
|
||||
formattedValue = "--";
|
||||
}
|
||||
else
|
||||
{
|
||||
if(QFieldType.STRING.equals(field.getType()) || field.getPossibleValueSourceName() != null)
|
||||
{
|
||||
formattedValue = '"' + formattedValue + '"';
|
||||
}
|
||||
}
|
||||
|
||||
return (formattedValue);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Map<Serializable, QRecord> buildOldRecordMap(QTableMetaData table, List<QRecord> oldRecordList)
|
||||
{
|
||||
Map<Serializable, QRecord> rs = new HashMap<>();
|
||||
for(QRecord record : CollectionUtils.nonNullList(oldRecordList))
|
||||
{
|
||||
rs.put(record.getValue(table.getPrimaryKeyField()), record);
|
||||
}
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private DMLType getDMLType(AbstractTableActionInput tableActionInput)
|
||||
{
|
||||
if(tableActionInput instanceof InsertInput)
|
||||
{
|
||||
return DMLType.INSERT;
|
||||
}
|
||||
else if(tableActionInput instanceof UpdateInput)
|
||||
{
|
||||
return DMLType.UPDATE;
|
||||
}
|
||||
else if(tableActionInput instanceof DeleteInput)
|
||||
{
|
||||
return DMLType.DELETE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DMLType.OTHER;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Map<String, Serializable> getRecordSecurityKeyValues(QTableMetaData table, QRecord record)
|
||||
{
|
||||
Map<String, Serializable> securityKeyValues = new HashMap<>();
|
||||
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(table.getRecordSecurityLocks()))
|
||||
{
|
||||
securityKeyValues.put(recordSecurityLock.getSecurityKeyType(), record == null ? null : record.getValue(recordSecurityLock.getFieldName()));
|
||||
}
|
||||
return securityKeyValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static AuditLevel getAuditLevel(AbstractTableActionInput tableActionInput)
|
||||
{
|
||||
QTableMetaData table = tableActionInput.getTable();
|
||||
if(table.getAuditRules() == null)
|
||||
{
|
||||
return (AuditLevel.NONE);
|
||||
}
|
||||
|
||||
return (table.getAuditRules().getAuditLevel());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private enum DMLType
|
||||
{
|
||||
INSERT("Inserted", true),
|
||||
UPDATE("Edited", true),
|
||||
DELETE("Deleted", false),
|
||||
OTHER("Processed", false);
|
||||
|
||||
private final String pastTenseVerb;
|
||||
private final boolean supportsFields;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
DMLType(String pastTenseVerb, boolean supportsFields)
|
||||
{
|
||||
this.pastTenseVerb = pastTenseVerb;
|
||||
this.supportsFields = supportsFields;
|
||||
}
|
||||
}
|
||||
}
|
@ -25,9 +25,18 @@ package com.kingsrook.qqq.backend.core.actions.automation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountOutput;
|
||||
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;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.automation.TableTrigger;
|
||||
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;
|
||||
@ -37,7 +46,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAuto
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.QLogger;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
|
||||
|
||||
@ -76,7 +84,7 @@ public class RecordAutomationStatusUpdater
|
||||
String className = stackTraceElement.getClassName();
|
||||
if(className.contains("com.kingsrook.qqq.backend.core.actions.automation") && !className.equals(RecordAutomationStatusUpdater.class.getName()) && !className.endsWith("Test"))
|
||||
{
|
||||
LOG.debug(session, "Avoiding re-setting automation status to PENDING_UPDATE while running an automation");
|
||||
LOG.debug("Avoiding re-setting automation status to PENDING_UPDATE while running an automation");
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
@ -119,14 +127,63 @@ public class RecordAutomationStatusUpdater
|
||||
|
||||
if(automationStatus.equals(AutomationStatus.PENDING_INSERT_AUTOMATIONS))
|
||||
{
|
||||
return tableActions.stream().noneMatch(a -> TriggerEvent.POST_INSERT.equals(a.getTriggerEvent()));
|
||||
if(tableActions.stream().anyMatch(a -> TriggerEvent.POST_INSERT.equals(a.getTriggerEvent())))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
else if(areThereTableTriggersForTable(table, TriggerEvent.POST_INSERT))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
else if(automationStatus.equals(AutomationStatus.PENDING_UPDATE_AUTOMATIONS))
|
||||
{
|
||||
return tableActions.stream().noneMatch(a -> TriggerEvent.POST_UPDATE.equals(a.getTriggerEvent()));
|
||||
if(tableActions.stream().anyMatch(a -> TriggerEvent.POST_UPDATE.equals(a.getTriggerEvent())))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
else if(areThereTableTriggersForTable(table, TriggerEvent.POST_UPDATE))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
|
||||
return (false);
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static boolean areThereTableTriggersForTable(QTableMetaData table, TriggerEvent triggerEvent)
|
||||
{
|
||||
if(QContext.getQInstance().getTable(TableTrigger.TABLE_NAME) == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
///////////////////
|
||||
// todo - cache? //
|
||||
///////////////////
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(TableTrigger.TABLE_NAME);
|
||||
countInput.setFilter(new QQueryFilter(
|
||||
new QFilterCriteria("tableName", QCriteriaOperator.EQUALS, table.getName()),
|
||||
new QFilterCriteria(triggerEvent.equals(TriggerEvent.POST_INSERT) ? "postInsert" : "postUpdate", QCriteriaOperator.EQUALS, true)
|
||||
));
|
||||
CountOutput countOutput = new CountAction().execute(countInput);
|
||||
return (countOutput.getCount() != null && countOutput.getCount() > 0);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the count query failed, we're a bit safer to err on the side of "yeah, there might be automations" //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -143,8 +200,7 @@ public class RecordAutomationStatusUpdater
|
||||
boolean didSetStatusField = setAutomationStatusInRecords(session, table, records, automationStatus);
|
||||
if(didSetStatusField)
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput(instance);
|
||||
updateInput.setSession(session);
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(table.getName());
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -156,6 +212,7 @@ public class RecordAutomationStatusUpdater
|
||||
.withValue(table.getPrimaryKeyField(), r.getValue(table.getPrimaryKeyField()))
|
||||
.withValue(automationDetails.getStatusTracking().getFieldName(), r.getValue(automationDetails.getStatusTracking().getFieldName()))).toList());
|
||||
updateInput.setAreAllValuesBeingUpdatedTheSame(true);
|
||||
updateInput.setOmitDmlAudit(true);
|
||||
|
||||
new UpdateAction().execute(updateInput);
|
||||
}
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.automation;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.RunAdHocRecordScriptAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
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.scripts.RunAdHocRecordScriptInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAdHocRecordScriptOutput;
|
||||
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;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class RunRecordScriptAutomationHandler extends RecordAutomationHandler
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(RunRecordScriptAutomationHandler.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void execute(RecordAutomationInput recordAutomationInput) throws QException
|
||||
{
|
||||
String tableName = recordAutomationInput.getTableName();
|
||||
Map<String, Serializable> values = recordAutomationInput.getAction().getValues();
|
||||
Integer scriptId = ValueUtils.getValueAsInteger(values.get("scriptId"));
|
||||
|
||||
if(scriptId == null)
|
||||
{
|
||||
throw (new QException("ScriptId was not provided in values map for record automations on table: " + tableName));
|
||||
}
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(ScriptRevision.TABLE_NAME);
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("scriptId", QCriteriaOperator.EQUALS, scriptId)));
|
||||
queryInput.withQueryJoin(new QueryJoin(Script.TABLE_NAME).withBaseTableOrAlias(ScriptRevision.TABLE_NAME).withJoinMetaData(QContext.getQInstance().getJoin("currentScriptRevision")));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
if(CollectionUtils.nullSafeIsEmpty(queryOutput.getRecords()))
|
||||
{
|
||||
throw (new QException("Could not find current revision for scriptId: " + scriptId + " on table " + tableName));
|
||||
}
|
||||
|
||||
QRecord scriptRevision = queryOutput.getRecords().get(0);
|
||||
LOG.info("Running script against records", logPair("scriptRevisionId", scriptRevision.getValue("id")), logPair("scriptId", scriptRevision.getValue("scriptIdd")));
|
||||
|
||||
RunAdHocRecordScriptInput input = new RunAdHocRecordScriptInput();
|
||||
input.setCodeReference(new AdHocScriptCodeReference().withScriptRevisionRecord(scriptRevision));
|
||||
input.setTableName(tableName);
|
||||
input.setRecordList(recordAutomationInput.getRecordList());
|
||||
RunAdHocRecordScriptOutput output = new RunAdHocRecordScriptOutput();
|
||||
new RunAdHocRecordScriptAction().run(input, output);
|
||||
}
|
||||
|
||||
}
|
@ -25,21 +25,24 @@ package com.kingsrook.qqq.backend.core.actions.automation.polling;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncRecordPipeLoop;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RunRecordScriptAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
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.processes.RunProcessInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
@ -47,21 +50,22 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.automation.RecordAutomationInput;
|
||||
import com.kingsrook.qqq.backend.core.model.automation.TableTrigger;
|
||||
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.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.AutomationStatusTrackingType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TriggerEvent;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.StandardScheduledExecutor;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -75,7 +79,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class PollingAutomationPerTableRunner implements Runnable
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(PollingAutomationPerTableRunner.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(PollingAutomationPerTableRunner.class);
|
||||
|
||||
private final TableActions tableActions;
|
||||
private final String name;
|
||||
@ -88,6 +92,11 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
TriggerEvent.POST_UPDATE, AutomationStatus.PENDING_UPDATE_AUTOMATIONS
|
||||
);
|
||||
|
||||
private static Map<AutomationStatus, TriggerEvent> automationStatusTriggerEventMap = Map.of(
|
||||
AutomationStatus.PENDING_INSERT_AUTOMATIONS, TriggerEvent.POST_INSERT,
|
||||
AutomationStatus.PENDING_UPDATE_AUTOMATIONS, TriggerEvent.POST_UPDATE
|
||||
);
|
||||
|
||||
private static Map<AutomationStatus, AutomationStatus> pendingToRunningStatusMap = Map.of(
|
||||
AutomationStatus.PENDING_INSERT_AUTOMATIONS, AutomationStatus.RUNNING_INSERT_AUTOMATIONS,
|
||||
AutomationStatus.PENDING_UPDATE_AUTOMATIONS, AutomationStatus.RUNNING_UPDATE_AUTOMATIONS
|
||||
@ -103,51 +112,26 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public record TableActions(String tableName, AutomationStatus status, List<TableAutomationAction> actions)
|
||||
public record TableActions(String tableName, AutomationStatus status)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** basically just get a list of tables which at least *could* have automations
|
||||
** run - either meta-data automations, or table-triggers (data/user defined).
|
||||
*******************************************************************************/
|
||||
public static List<TableActions> getTableActions(QInstance instance, String providerName)
|
||||
{
|
||||
Map<String, Map<AutomationStatus, List<TableAutomationAction>>> workingTableActionMap = new HashMap<>();
|
||||
List<TableActions> tableActionList = new ArrayList<>();
|
||||
List<TableActions> tableActionList = new ArrayList<>();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// todo - share logic like this among any automation implementation //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
for(QTableMetaData table : instance.getTables().values())
|
||||
{
|
||||
if(table.getAutomationDetails() != null && providerName.equals(table.getAutomationDetails().getProviderName()))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// organize the table's actions by type //
|
||||
// todo - in future, need user-defined actions here too (and refreshed!) //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
for(TableAutomationAction action : table.getAutomationDetails().getActions())
|
||||
{
|
||||
AutomationStatus automationStatus = triggerEventAutomationStatusMap.get(action.getTriggerEvent());
|
||||
workingTableActionMap.putIfAbsent(table.getName(), new HashMap<>());
|
||||
workingTableActionMap.get(table.getName()).putIfAbsent(automationStatus, new ArrayList<>());
|
||||
workingTableActionMap.get(table.getName()).get(automationStatus).add(action);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
// convert the map to tableAction records //
|
||||
////////////////////////////////////////////
|
||||
for(Map.Entry<AutomationStatus, List<TableAutomationAction>> entry : workingTableActionMap.get(table.getName()).entrySet())
|
||||
{
|
||||
AutomationStatus automationStatus = entry.getKey();
|
||||
List<TableAutomationAction> actionList = entry.getValue();
|
||||
|
||||
actionList.sort(Comparator.comparing(TableAutomationAction::getPriority));
|
||||
|
||||
tableActionList.add(new TableActions(table.getName(), automationStatus, actionList));
|
||||
}
|
||||
tableActionList.add(new TableActions(table.getName(), AutomationStatus.PENDING_INSERT_AUTOMATIONS));
|
||||
tableActionList.add(new TableActions(table.getName(), AutomationStatus.PENDING_UPDATE_AUTOMATIONS));
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,14 +159,16 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
QContext.init(instance, sessionSupplier.get());
|
||||
|
||||
String originalThreadName = Thread.currentThread().getName();
|
||||
Thread.currentThread().setName(name + StandardScheduledExecutor.newThreadNameRandomSuffix());
|
||||
Thread.currentThread().setName(name);
|
||||
LOG.debug("Running " + this.getClass().getSimpleName() + "[" + name + "]");
|
||||
|
||||
try
|
||||
{
|
||||
QSession session = sessionSupplier != null ? sessionSupplier.get() : new QSession();
|
||||
processTableInsertOrUpdate(instance.getTable(tableActions.tableName()), session, tableActions.status(), tableActions.actions());
|
||||
processTableInsertOrUpdate(instance.getTable(tableActions.tableName()), session, tableActions.status());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
@ -191,6 +177,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
finally
|
||||
{
|
||||
Thread.currentThread().setName(originalThreadName);
|
||||
QContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,8 +186,12 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
/*******************************************************************************
|
||||
** Query for and process records that have a PENDING_INSERT or PENDING_UPDATE status on a given table.
|
||||
*******************************************************************************/
|
||||
private void processTableInsertOrUpdate(QTableMetaData table, QSession session, AutomationStatus automationStatus, List<TableAutomationAction> actions) throws QException
|
||||
public void processTableInsertOrUpdate(QTableMetaData table, QSession session, AutomationStatus automationStatus) throws QException
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// get the actions to run against this table in this automation status //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
List<TableAutomationAction> actions = getTableActions(table, automationStatus);
|
||||
if(CollectionUtils.nullSafeIsEmpty(actions))
|
||||
{
|
||||
return;
|
||||
@ -219,8 +210,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
|
||||
asyncRecordPipeLoop.run("PollingAutomationRunner>Query>" + automationStatus + ">" + table.getName(), null, recordPipe, (status) ->
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(instance);
|
||||
queryInput.setSession(session);
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(table.getName());
|
||||
|
||||
AutomationStatusTrackingType statusTrackingType = automationDetails.getStatusTracking().getType();
|
||||
@ -246,6 +236,59 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** get the actions to run against a table in an automation status. both from
|
||||
** metaData and tableTriggers/data.
|
||||
*******************************************************************************/
|
||||
private List<TableAutomationAction> getTableActions(QTableMetaData table, AutomationStatus automationStatus) throws QException
|
||||
{
|
||||
List<TableAutomationAction> rs = new ArrayList<>();
|
||||
TriggerEvent triggerEvent = automationStatusTriggerEventMap.get(automationStatus);
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// start with any actions defined in the table meta data //
|
||||
///////////////////////////////////////////////////////////
|
||||
for(TableAutomationAction action : table.getAutomationDetails().getActions())
|
||||
{
|
||||
if(action.getTriggerEvent().equals(triggerEvent))
|
||||
{
|
||||
rs.add(action);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// next add any tableTriggers, defined in data //
|
||||
/////////////////////////////////////////////////
|
||||
if(QContext.getQInstance().getTable(TableTrigger.TABLE_NAME) != null)
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(TableTrigger.TABLE_NAME);
|
||||
queryInput.setFilter(new QQueryFilter(
|
||||
new QFilterCriteria("tableName", QCriteriaOperator.EQUALS, table.getName()),
|
||||
new QFilterCriteria(triggerEvent.equals(TriggerEvent.POST_INSERT) ? "postInsert" : "postUpdate", QCriteriaOperator.EQUALS, true)
|
||||
));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
// todo - get filter if there is/was one
|
||||
rs.add(new TableAutomationAction()
|
||||
.withName("Script:" + record.getValue("scriptId"))
|
||||
.withFilter(null)
|
||||
.withTriggerEvent(triggerEvent)
|
||||
.withPriority(record.getValueInteger("priority"))
|
||||
.withCodeReference(new QCodeReference(RunRecordScriptAutomationHandler.class))
|
||||
.withValues(MapBuilder.of("scriptId", record.getValue("scriptId")))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
rs.sort(Comparator.comparing(taa -> Objects.requireNonNullElse(taa.getPriority(), Integer.MAX_VALUE)));
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a set of records that were found to be in a PENDING state - run all the
|
||||
** table's actions against them - IF they are found to match the action's filter
|
||||
@ -274,12 +317,12 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// note - this method - will re-query the objects, so we should have confidence that their data is fresh... //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> matchingQRecords = getRecordsMatchingActionFilter(session, table, records, action);
|
||||
List<QRecord> matchingQRecords = getRecordsMatchingActionFilter(table, records, action);
|
||||
LOG.debug("Of the {} records that were pending automations, {} of them match the filter on the action {}", records.size(), matchingQRecords.size(), action);
|
||||
if(CollectionUtils.nullSafeHasContents(matchingQRecords))
|
||||
{
|
||||
LOG.debug(" Processing " + matchingQRecords.size() + " records in " + table + " for action " + action);
|
||||
applyActionToMatchingRecords(instance, session, table, matchingQRecords, action);
|
||||
applyActionToMatchingRecords(table, matchingQRecords, action);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
@ -316,10 +359,9 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
** but that will almost certainly give potentially different results than a true
|
||||
** backend - e.g., just consider if the DB is case-sensitive for strings...
|
||||
*******************************************************************************/
|
||||
private List<QRecord> getRecordsMatchingActionFilter(QSession session, QTableMetaData table, List<QRecord> records, TableAutomationAction action) throws QException
|
||||
private List<QRecord> getRecordsMatchingActionFilter(QTableMetaData table, List<QRecord> records, TableAutomationAction action) throws QException
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(instance);
|
||||
queryInput.setSession(session);
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(table.getName());
|
||||
|
||||
QQueryFilter filter = new QQueryFilter();
|
||||
@ -359,7 +401,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
** Finally, actually run action code against a list of known matching records.
|
||||
** todo not commit - move to somewhere genericer
|
||||
*******************************************************************************/
|
||||
public static void applyActionToMatchingRecords(QInstance instance, QSession session, QTableMetaData table, List<QRecord> records, TableAutomationAction action) throws Exception
|
||||
public static void applyActionToMatchingRecords(QTableMetaData table, List<QRecord> records, TableAutomationAction action) throws Exception
|
||||
{
|
||||
if(StringUtils.hasContent(action.getProcessName()))
|
||||
{
|
||||
@ -368,8 +410,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
// tell it to SKIP frontend steps. //
|
||||
// give the process a callback w/ a query filter that has the p-keys of these records. //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
RunProcessInput runProcessInput = new RunProcessInput(instance);
|
||||
runProcessInput.setSession(session);
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(action.getProcessName());
|
||||
runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
||||
runProcessInput.setCallback(new QProcessCallback()
|
||||
@ -382,20 +423,29 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
}
|
||||
});
|
||||
|
||||
RunProcessAction runProcessAction = new RunProcessAction();
|
||||
RunProcessOutput runProcessOutput = runProcessAction.execute(runProcessInput);
|
||||
if(runProcessOutput.getException().isPresent())
|
||||
try
|
||||
{
|
||||
throw (runProcessOutput.getException().get());
|
||||
QContext.pushAction(runProcessInput);
|
||||
|
||||
RunProcessAction runProcessAction = new RunProcessAction();
|
||||
RunProcessOutput runProcessOutput = runProcessAction.execute(runProcessInput);
|
||||
if(runProcessOutput.getException().isPresent())
|
||||
{
|
||||
throw (runProcessOutput.getException().get());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.popAction();
|
||||
}
|
||||
}
|
||||
else if(action.getCodeReference() != null)
|
||||
{
|
||||
LOG.debug(" Executing action: [" + action.getName() + "] as code reference: " + action.getCodeReference());
|
||||
RecordAutomationInput input = new RecordAutomationInput(instance);
|
||||
input.setSession(session);
|
||||
RecordAutomationInput input = new RecordAutomationInput();
|
||||
input.setTableName(table.getName());
|
||||
input.setRecordList(records);
|
||||
input.setAction(action);
|
||||
|
||||
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getRecordAutomationHandler(action);
|
||||
recordAutomationHandler.execute(input);
|
||||
|
@ -113,8 +113,7 @@ public abstract class ChildInserterPostInsertCustomizer extends AbstractPostInse
|
||||
/////////////////////////
|
||||
// insert the children //
|
||||
/////////////////////////
|
||||
InsertInput insertInput = new InsertInput(getInsertInput().getInstance());
|
||||
insertInput.setSession(getInsertInput().getSession());
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(getChildTableName());
|
||||
insertInput.setRecords(childrenToInsert);
|
||||
insertInput.setTransaction(this.insertInput.getTransaction());
|
||||
@ -145,8 +144,7 @@ public abstract class ChildInserterPostInsertCustomizer extends AbstractPostInse
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// update the originally inserted records to reference their new children //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
UpdateInput updateInput = new UpdateInput(insertInput.getInstance());
|
||||
updateInput.setSession(getInsertInput().getSession());
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(getInsertInput().getTableName());
|
||||
updateInput.setRecords(recordsToUpdate);
|
||||
updateInput.setTransaction(this.insertInput.getTransaction());
|
||||
|
@ -28,13 +28,13 @@ import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -42,7 +42,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class QCodeLoader
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QCodeLoader.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(QCodeLoader.class);
|
||||
|
||||
|
||||
|
||||
@ -102,7 +102,7 @@ public class QCodeLoader
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error initializing customizer: " + codeReference);
|
||||
LOG.error("Error initializing customizer", logPair("codeReference", codeReference), e);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||
@ -141,7 +141,7 @@ public class QCodeLoader
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error initializing customizer: " + codeReference);
|
||||
LOG.error("Error initializing customizer", logPair("codeReference", codeReference), e);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||
@ -180,7 +180,7 @@ public class QCodeLoader
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error initializing customizer: " + codeReference);
|
||||
LOG.error("Error initializing customizer", logPair("codeReference", codeReference), e);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||
|
@ -26,12 +26,14 @@ import java.io.Serializable;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@ -53,7 +55,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected String openTopLevelBulletList()
|
||||
public static String openTopLevelBulletList()
|
||||
{
|
||||
return ("""
|
||||
<div style="padding-left: 2rem;">
|
||||
@ -65,7 +67,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected String closeTopLevelBulletList()
|
||||
public static String closeTopLevelBulletList()
|
||||
{
|
||||
return ("""
|
||||
</ul>
|
||||
@ -119,7 +121,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
*******************************************************************************/
|
||||
public static String linkTableBulkLoad(RenderWidgetInput input, String tableName) throws QException
|
||||
{
|
||||
String tablePath = input.getInstance().getTablePath(input, tableName);
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
return (tablePath + "/" + tableName + ".bulkInsert");
|
||||
}
|
||||
|
||||
@ -128,8 +130,14 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkTableBulkLoadChildren(String tableName) throws QException
|
||||
public static String linkTableBulkLoadChildren(RenderWidgetInput input, String tableName) throws QException
|
||||
{
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
if(tablePath == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return ("#/launchProcess=" + tableName + ".bulkInsert");
|
||||
}
|
||||
|
||||
@ -140,7 +148,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
*******************************************************************************/
|
||||
public static String linkTableCreate(RenderWidgetInput input, String tableName) throws QException
|
||||
{
|
||||
String tablePath = input.getInstance().getTablePath(input, tableName);
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
return (tablePath + "/create");
|
||||
}
|
||||
|
||||
@ -151,18 +159,71 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
*******************************************************************************/
|
||||
public static String linkTableCreateWithDefaultValues(RenderWidgetInput input, String tableName, Map<String, Serializable> defaultValues) throws QException
|
||||
{
|
||||
String tablePath = input.getInstance().getTablePath(input, tableName);
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
return (tablePath + "/create?defaultValues=" + URLEncoder.encode(JsonUtils.toJson(defaultValues), Charset.defaultCharset()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String getCountLink(RenderWidgetInput input, String tableName, QQueryFilter filter, int count) throws QException
|
||||
{
|
||||
String totalString = QValueFormatter.formatValue(DisplayFormat.COMMAS, count);
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
if(tablePath == null || filter == null)
|
||||
{
|
||||
return (totalString);
|
||||
}
|
||||
return ("<a href='" + tablePath + "?filter=" + JsonUtils.toJson(filter) + "'>" + totalString + "</a>");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void addTableFilterToListIfPermissed(RenderWidgetInput input, String tableName, List<String> urls, QQueryFilter filter) throws QException
|
||||
{
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
if(tablePath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
urls.add(tablePath + "?filter=" + JsonUtils.toJson(filter));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkTableFilterUnencoded(RenderWidgetInput input, String tableName, QQueryFilter filter) throws QException
|
||||
{
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
if(tablePath == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return (tablePath + "?filter=" + JsonUtils.toJson(filter));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkTableFilter(RenderWidgetInput input, String tableName, QQueryFilter filter) throws QException
|
||||
{
|
||||
String tablePath = input.getInstance().getTablePath(input, tableName);
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
if(tablePath == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
|
||||
}
|
||||
|
||||
@ -173,8 +234,34 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
*******************************************************************************/
|
||||
public static String aHrefTableFilterNoOfRecords(RenderWidgetInput input, String tableName, QQueryFilter filter, Integer noOfRecords, String singularLabel, String pluralLabel) throws QException
|
||||
{
|
||||
return (aHrefTableFilterNoOfRecords(input, tableName, filter, noOfRecords, singularLabel, pluralLabel, false));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String aHrefTableFilterNoOfRecords(RenderWidgetInput input, String tableName, QQueryFilter filter, Integer noOfRecords, String singularLabel, String pluralLabel, boolean onlyLinkCount) throws QException
|
||||
{
|
||||
String plural = StringUtils.plural(noOfRecords, singularLabel, pluralLabel);
|
||||
String countString = QValueFormatter.formatValue(DisplayFormat.COMMAS, noOfRecords);
|
||||
String displayText = StringUtils.hasContent(plural) ? (" " + plural) : "";
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
if(tablePath == null)
|
||||
{
|
||||
return (countString + displayText);
|
||||
}
|
||||
|
||||
String href = linkTableFilter(input, tableName, filter);
|
||||
return ("<a href=\"" + href + "\">" + QValueFormatter.formatValue(DisplayFormat.COMMAS, noOfRecords) + " " + StringUtils.plural(noOfRecords, singularLabel, pluralLabel) + "</a>");
|
||||
if(onlyLinkCount)
|
||||
{
|
||||
return ("<a href=\"" + href + "\">" + countString + "</a>" + displayText);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ("<a href=\"" + href + "\">" + countString + displayText + "</a>");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -184,6 +271,12 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
*******************************************************************************/
|
||||
public static String aHrefViewRecord(RenderWidgetInput input, String tableName, Serializable id, String linkText) throws QException
|
||||
{
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
if(tablePath == null)
|
||||
{
|
||||
return (linkText);
|
||||
}
|
||||
|
||||
return ("<a href=\"" + linkRecordView(input, tableName, id) + "\">" + linkText + "</a>");
|
||||
}
|
||||
|
||||
@ -194,7 +287,7 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
*******************************************************************************/
|
||||
public static String linkRecordEdit(AbstractActionInput input, String tableName, Serializable recordId) throws QException
|
||||
{
|
||||
String tablePath = input.getInstance().getTablePath(input, tableName);
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
return (tablePath + "/" + recordId + "/edit");
|
||||
}
|
||||
|
||||
@ -205,20 +298,48 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
*******************************************************************************/
|
||||
public static String linkRecordView(AbstractActionInput input, String tableName, Serializable recordId) throws QException
|
||||
{
|
||||
String tablePath = input.getInstance().getTablePath(input, tableName);
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
if(tablePath == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return (tablePath + "/" + recordId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkProcessForFilter(AbstractActionInput input, String processName, QQueryFilter filter) throws QException
|
||||
{
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
if(process == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
String tableName = process.getTableName();
|
||||
if(tableName == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
return (tablePath + "/" + processName + "?recordsParam=filterJSON&filterJSON=" + URLEncoder.encode(JsonUtils.toJson(filter), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkProcessForRecord(AbstractActionInput input, String processName, Serializable recordId) throws QException
|
||||
{
|
||||
QProcessMetaData process = input.getInstance().getProcess(processName);
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
String tableName = process.getTableName();
|
||||
String tablePath = input.getInstance().getTablePath(input, tableName);
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
|
||||
return (tablePath + "/" + recordId + "/" + processName);
|
||||
}
|
||||
|
||||
@ -227,9 +348,9 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkTableCreateChild(String childTableName, Map<String, Serializable> defaultValues)
|
||||
public static String linkTableCreateChild(RenderWidgetInput input, String childTableName, Map<String, Serializable> defaultValues) throws QException
|
||||
{
|
||||
return (linkTableCreateChild(childTableName, defaultValues, defaultValues.keySet()));
|
||||
return (linkTableCreateChild(input, childTableName, defaultValues, defaultValues.keySet()));
|
||||
}
|
||||
|
||||
|
||||
@ -237,9 +358,9 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String aHrefTableCreateChild(String childTableName, Map<String, Serializable> defaultValues)
|
||||
public static String aHrefTableCreateChild(RenderWidgetInput input, String childTableName, Map<String, Serializable> defaultValues) throws QException
|
||||
{
|
||||
return (aHrefTableCreateChild(childTableName, defaultValues, defaultValues.keySet()));
|
||||
return (aHrefTableCreateChild(input, childTableName, defaultValues, defaultValues.keySet()));
|
||||
}
|
||||
|
||||
|
||||
@ -247,8 +368,14 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkTableCreateChild(String childTableName, Map<String, Serializable> defaultValues, Set<String> disabledFields)
|
||||
public static String linkTableCreateChild(RenderWidgetInput input, String childTableName, Map<String, Serializable> defaultValues, Set<String> disabledFields) throws QException
|
||||
{
|
||||
String tablePath = QContext.getQInstance().getTablePath(childTableName);
|
||||
if(tablePath == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
Map<String, Integer> disabledFieldsMap = disabledFields.stream().collect(Collectors.toMap(k -> k, k -> 1));
|
||||
|
||||
return ("#/createChild=" + childTableName
|
||||
@ -261,9 +388,35 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String aHrefTableCreateChild(String childTableName, Map<String, Serializable> defaultValues, Set<String> disabledFields)
|
||||
public static String getChipElement(String icon, String label, String color) throws QException
|
||||
{
|
||||
return ("<a href=\"" + linkTableCreateChild(childTableName, defaultValues, defaultValues.keySet()) + "\">Create new</a>");
|
||||
color = color != null ? color : "info";
|
||||
color = StringUtils.ucFirst(color);
|
||||
|
||||
String html = "<span style='display: flex;'>";
|
||||
html += "<div style='overflow: hidden; flex: none; display: flex; align-content: flex-start; align-items: center; height: 24px; padding-right: 8px; font-size: 13px; font-weight: 500; border: 1px solid; border-radius: 16px; color: " + color + "'>";
|
||||
if(icon != null)
|
||||
{
|
||||
html += "<span style='font-size: 16px; padding: 5px' class='material-icons-round notranslate MuiIcon-root MuiIcon-fontSizeInherit MuiChip-icon MuiChip-iconSmall MuiChip-iconColor" + color + "'>" + icon + "</span>";
|
||||
}
|
||||
html += "<span class='MuiChip-label MuiChip-labelSmall'>" + label + "</span></div></span>";
|
||||
return (html);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String aHrefTableCreateChild(RenderWidgetInput input, String childTableName, Map<String, Serializable> defaultValues, Set<String> disabledFields) throws QException
|
||||
{
|
||||
String tablePath = QContext.getQInstance().getTablePath(childTableName);
|
||||
if(tablePath == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return ("<a href=\"" + linkTableCreateChild(input, childTableName, defaultValues, defaultValues.keySet()) + "\">Create new</a>");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -89,8 +88,7 @@ public abstract class AbstractWidgetRenderer
|
||||
pvsLabels.add(pvsLabel);
|
||||
pvsNames.add(possibleValueSourceName);
|
||||
|
||||
SearchPossibleValueSourceInput pvsInput = new SearchPossibleValueSourceInput(input.getInstance());
|
||||
pvsInput.setSession(input.getSession());
|
||||
SearchPossibleValueSourceInput pvsInput = new SearchPossibleValueSourceInput();
|
||||
pvsInput.setPossibleValueSourceName(possibleValueSourceName);
|
||||
|
||||
if(dropdownData.getForeignKeyFieldName() != null)
|
||||
@ -123,7 +121,6 @@ public abstract class AbstractWidgetRenderer
|
||||
//////////////////////////////////////////
|
||||
Set<String> exists = new HashSet<>();
|
||||
output.getResults().removeIf(pvs -> !exists.add(pvs.getLabel()));
|
||||
output.getResults().sort(Comparator.comparing(QPossibleValue::getLabel));
|
||||
for(QPossibleValue<?> possibleValue : output.getResults())
|
||||
{
|
||||
dropdownOptionList.add(Map.of(
|
||||
|
@ -53,6 +53,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
|
||||
@ -70,6 +71,7 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
{
|
||||
return (new Builder(new QWidgetMetaData()
|
||||
.withName(join.getName())
|
||||
.withIsCard(true)
|
||||
.withCodeReference(new QCodeReference(ChildRecordListRenderer.class, null))
|
||||
.withType(WidgetType.CHILD_RECORD_LIST.getType())
|
||||
.withDefaultValue("joinName", join.getName())));
|
||||
@ -116,6 +118,17 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Builder withMaxRows(Integer maxRows)
|
||||
{
|
||||
widgetMetaData.withDefaultValue("maxRows", maxRows);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -150,12 +163,17 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
QJoinMetaData join = input.getInstance().getJoin(joinName);
|
||||
String id = input.getQueryParams().get("id");
|
||||
|
||||
Integer maxRows = null;
|
||||
if(StringUtils.hasContent(input.getQueryParams().get("maxRows")))
|
||||
{
|
||||
maxRows = ValueUtils.getValueAsInteger(input.getQueryParams().get("maxRows"));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// fetch the record that we're getting children for. //
|
||||
// e.g., the left-side of the join, with the input id //
|
||||
////////////////////////////////////////////////////////
|
||||
GetInput getInput = new GetInput(input.getInstance());
|
||||
getInput.setSession(input.getSession());
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(join.getLeftTable());
|
||||
getInput.setPrimaryKey(id);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
@ -177,16 +195,16 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
|
||||
}
|
||||
filter.setOrderBys(join.getOrderBys());
|
||||
|
||||
QueryInput queryInput = new QueryInput(input.getInstance());
|
||||
queryInput.setSession(input.getSession());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(join.getRightTable());
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setShouldGenerateDisplayValues(true);
|
||||
queryInput.setFilter(filter);
|
||||
queryInput.setLimit(maxRows);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
QTableMetaData table = input.getInstance().getTable(join.getRightTable());
|
||||
String tablePath = input.getInstance().getTablePath(input, table.getName());
|
||||
String tablePath = input.getInstance().getTablePath(table.getName());
|
||||
String viewAllLink = tablePath == null ? null : (tablePath + "?filter=" + URLEncoder.encode(JsonUtils.toJson(filter), Charset.defaultCharset()));
|
||||
|
||||
ChildRecordListData widgetData = new ChildRecordListData(widgetLabel, queryOutput, table, tablePath, viewAllLink);
|
||||
|
@ -0,0 +1,249 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
|
||||
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.IsoFields;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Enum to define various "levels" of group-by for on dashboards that want to
|
||||
** group records by, e.g., year, or month, or week, or day, or hour.
|
||||
*******************************************************************************/
|
||||
public enum DateTimeGroupBy
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// note - double %'s on the time format strings here, because this is a java-format string, which will get //
|
||||
// its '%s' replaced with a column name, and so then those %'s for the date_format need escaped as %%. //
|
||||
// See https://www.w3schools.com/sql/func_mysql_date_format.asp for DATE_FORMAT args //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
YEAR("%%Y", MillisPer.YEAR, 1, ChronoUnit.YEARS, DateTimeFormatter.ofPattern("yyyy"), DateTimeFormatter.ofPattern("yyyy")),
|
||||
MONTH("%%Y-%%m", 2 * MillisPer.MONTH, 1, ChronoUnit.MONTHS, DateTimeFormatter.ofPattern("yyyy-MM"), DateTimeFormatter.ofPattern("MMM'.' yyyy")),
|
||||
WEEK("%%XW%%V", 35 * MillisPer.DAY, 7, ChronoUnit.DAYS, DateTimeFormatter.ofPattern("YYYY'W'ww"), DateTimeFormatter.ofPattern("YYYY'W'w")),
|
||||
DAY("%%Y-%%m-%%d", 36 * MillisPer.HOUR, 1, ChronoUnit.DAYS, DateTimeFormatter.ofPattern("yyyy-MM-dd"), DateTimeFormatter.ofPattern("EEE'.' M'/'d")),
|
||||
HOUR("%%Y-%%m-%%dT%%H", 0, 1, ChronoUnit.HOURS, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH"), DateTimeFormatter.ofPattern("h a"));
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface MillisPer
|
||||
{
|
||||
long HOUR = 60 * 60 * 1000;
|
||||
long DAY = 24 * HOUR;
|
||||
long WEEK = 7 * DAY;
|
||||
long MONTH = 30 * DAY;
|
||||
long YEAR = 365 * DAY;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private final String sqlDateFormat;
|
||||
private final long millisThreshold;
|
||||
private final int noOfChronoUnitsToAdd;
|
||||
private final ChronoUnit chronoUnitToAdd;
|
||||
private final DateTimeFormatter selectedStringFormatter;
|
||||
private final DateTimeFormatter humanStringFormatter;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
DateTimeGroupBy(String sqlDateFormat, long millisThreshold, int noOfChronoUnitsToAdd, ChronoUnit chronoUnitToAdd, DateTimeFormatter selectedStringFormatter, DateTimeFormatter humanStringFormatter)
|
||||
{
|
||||
this.sqlDateFormat = sqlDateFormat;
|
||||
this.millisThreshold = millisThreshold;
|
||||
this.noOfChronoUnitsToAdd = noOfChronoUnitsToAdd;
|
||||
this.chronoUnitToAdd = chronoUnitToAdd;
|
||||
this.selectedStringFormatter = selectedStringFormatter;
|
||||
this.humanStringFormatter = humanStringFormatter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getSqlExpression()
|
||||
{
|
||||
ZoneId sessionOrInstanceZoneId = ValueUtils.getSessionOrInstanceZoneId();
|
||||
String targetTimezone = sessionOrInstanceZoneId.toString();
|
||||
|
||||
if("Z".equals(targetTimezone) || !StringUtils.hasContent(targetTimezone))
|
||||
{
|
||||
targetTimezone = "UTC";
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we only had a timezone offset (not a zone name/id), then the zoneId's toString will look like //
|
||||
// UTC-05:00. MySQL doesn't want that, so, strip away the leading UTC, to just get -05:00 //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if((targetTimezone.startsWith("UTC-") || targetTimezone.startsWith("UTC+")) && targetTimezone.length() > 5)
|
||||
{
|
||||
targetTimezone = targetTimezone.substring(3);
|
||||
}
|
||||
|
||||
return "DATE_FORMAT(CONVERT_TZ(%s, 'UTC', '" + targetTimezone + "'), '" + sqlDateFormat + "')";
|
||||
|
||||
/*
|
||||
if(this == WEEK)
|
||||
{
|
||||
return "YEARWEEK(CONVERT_TZ(%s, 'UTC', '" + targetTimezone + "'), 6)";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "DATE_FORMAT(CONVERT_TZ(%s, 'UTC', '" + targetTimezone + "'), '" + sqlDateFormat + "')";
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** get an instance of this enum, based on start & end instants - look at the #
|
||||
** of millis between them, and return the first enum value w/ a millisThreshold
|
||||
** under that difference. Default to HOUR.
|
||||
*******************************************************************************/
|
||||
public static DateTimeGroupBy selectFromStartAndEndTimes(Instant start, Instant end)
|
||||
{
|
||||
long millisBetween = end.toEpochMilli() - start.toEpochMilli();
|
||||
for(DateTimeGroupBy value : DateTimeGroupBy.values())
|
||||
{
|
||||
if(millisBetween > value.millisThreshold)
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
}
|
||||
|
||||
return (HOUR);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Make an Instant into a string that will match what came out of the database's
|
||||
** DATE_FORMAT() function
|
||||
*******************************************************************************/
|
||||
public String makeSelectedString(Instant time)
|
||||
{
|
||||
ZonedDateTime zoned = time.atZone(ValueUtils.getSessionOrInstanceZoneId());
|
||||
|
||||
if(this == WEEK)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// so, it seems like database is returning, e.g., W00-W52, but java is doing W1-W53... //
|
||||
// which, apparently we can compensate for by adding a week? not sure, but results seemed right. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
zoned = zoned.plusDays(7);
|
||||
int weekYear = zoned.get(IsoFields.WEEK_BASED_YEAR);
|
||||
int week = zoned.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
|
||||
return (String.format("%04dW%02d", weekYear, week));
|
||||
}
|
||||
|
||||
return (selectedStringFormatter.format(zoned));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Make a string to show to a user
|
||||
*******************************************************************************/
|
||||
public String makeHumanString(Instant instant)
|
||||
{
|
||||
ZonedDateTime zoned = instant.atZone(ValueUtils.getSessionOrInstanceZoneId());
|
||||
if(this.equals(WEEK))
|
||||
{
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("M'/'d");
|
||||
|
||||
while(zoned.get(ChronoField.DAY_OF_WEEK) != DayOfWeek.SUNDAY.getValue())
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// go backwards until sunday is found //
|
||||
////////////////////////////////////////
|
||||
zoned = zoned.minus(1, ChronoUnit.DAYS);
|
||||
}
|
||||
|
||||
return (dateTimeFormatter.format(zoned) + "-" + dateTimeFormatter.format(zoned.plusDays(6)));
|
||||
|
||||
/*
|
||||
int weekOfYear = zoned.get(ChronoField.ALIGNED_WEEK_OF_YEAR);
|
||||
ZonedDateTime sunday = zoned.with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, weekOfYear).with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY));
|
||||
ZonedDateTime saturday = sunday.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("M'/'d");
|
||||
|
||||
return (dateTimeFormatter.format(sunday) + "-" + dateTimeFormatter.format(saturday));
|
||||
*/
|
||||
}
|
||||
|
||||
return (humanStringFormatter.format(zoned));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:indentation")
|
||||
public Instant roundDown(Instant instant)
|
||||
{
|
||||
ZonedDateTime zoned = instant.atZone(ValueUtils.getSessionOrInstanceZoneId());
|
||||
return switch(this)
|
||||
{
|
||||
case YEAR -> zoned.with(TemporalAdjusters.firstDayOfYear()).truncatedTo(ChronoUnit.DAYS).toInstant();
|
||||
case MONTH -> zoned.with(TemporalAdjusters.firstDayOfMonth()).truncatedTo(ChronoUnit.DAYS).toInstant();
|
||||
case WEEK ->
|
||||
{
|
||||
while(zoned.get(ChronoField.DAY_OF_WEEK) != DayOfWeek.SUNDAY.getValue())
|
||||
{
|
||||
zoned = zoned.minusDays(1);
|
||||
}
|
||||
yield (zoned.truncatedTo(ChronoUnit.DAYS).toInstant());
|
||||
}
|
||||
case DAY -> zoned.truncatedTo(ChronoUnit.DAYS).toInstant();
|
||||
case HOUR -> zoned.truncatedTo(ChronoUnit.HOURS).toInstant();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Instant increment(Instant instant)
|
||||
{
|
||||
ZonedDateTime zoned = instant.atZone(ValueUtils.getSessionOrInstanceZoneId());
|
||||
return (zoned.plus(noOfChronoUnitsToAdd, chronoUnitToAdd).toInstant());
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
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.QWidgetData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class DefaultWidgetRenderer extends AbstractWidgetRenderer
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
|
||||
{
|
||||
return new RenderWidgetOutput(new DefaultWidgetData(input));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static class DefaultWidgetData extends QWidgetData
|
||||
{
|
||||
private final String type;
|
||||
private final Map<String, String> queryParams;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public DefaultWidgetData(RenderWidgetInput renderWidgetInput)
|
||||
{
|
||||
this.type = renderWidgetInput.getWidgetMetaData().getType();
|
||||
this.queryParams = renderWidgetInput.getQueryParams();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String getType()
|
||||
{
|
||||
return (type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryParams
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, String> getQueryParams()
|
||||
{
|
||||
return queryParams;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.query.QFilterCriteria;
|
||||
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.RawHTML;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.AbstractWidgetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.AbstractWidgetValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.QNoCodeWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class NoCodeWidgetRenderer extends AbstractWidgetRenderer
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(NoCodeWidgetRenderer.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public RenderWidgetOutput render(RenderWidgetInput input) throws QException
|
||||
{
|
||||
QNoCodeWidgetMetaData widgetMetaData = (QNoCodeWidgetMetaData) input.getWidgetMetaData();
|
||||
|
||||
Map<String, Object> context = initContext(input);
|
||||
context.putAll(input.getQueryParams());
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// populate context by evaluating all values //
|
||||
///////////////////////////////////////////////
|
||||
for(AbstractWidgetValueSource valueSource : widgetMetaData.getValues())
|
||||
{
|
||||
try
|
||||
{
|
||||
LOG.trace("Computing: " + valueSource.getType() + " named " + valueSource.getName() + "...");
|
||||
Object value = valueSource.evaluate(context, input);
|
||||
LOG.trace("Computed: " + valueSource.getName() + " = " + value);
|
||||
context.put(valueSource.getName(), value);
|
||||
context.put(valueSource.getName() + ".source", valueSource);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error evaluating widget value source", e, logPair("widgetName", input.getWidgetMetaData().getName()), logPair("valueSourceName", valueSource.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////
|
||||
// build content by evaluating all outputs //
|
||||
/////////////////////////////////////////////
|
||||
List<AbstractWidgetOutput> outputs = widgetMetaData.getOutputs();
|
||||
String content = renderOutputs(context, outputs);
|
||||
|
||||
return (new RenderWidgetOutput(new RawHTML(widgetMetaData.getLabel(), content)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, Object> initContext(RenderWidgetInput input)
|
||||
{
|
||||
Map<String, Object> context = new HashMap<>();
|
||||
context.put("utils", new NoCodeWidgetVelocityUtils(context, input));
|
||||
context.put("input", input);
|
||||
return context;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String renderOutputs(Map<String, Object> context, List<AbstractWidgetOutput> outputs) throws QException
|
||||
{
|
||||
StringBuilder content = new StringBuilder();
|
||||
for(AbstractWidgetOutput output : CollectionUtils.nonNullList(outputs))
|
||||
{
|
||||
boolean conditionPassed = true;
|
||||
if(output.getCondition() != null)
|
||||
{
|
||||
conditionPassed = evaluateCondition(output.getCondition(), context);
|
||||
}
|
||||
|
||||
if(conditionPassed)
|
||||
{
|
||||
String render = output.render(context);
|
||||
content.append(render);
|
||||
LOG.trace("Condition passed, rendered: " + render);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.trace("Condition failed - not rendering this output.");
|
||||
}
|
||||
}
|
||||
return (content.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private boolean evaluateCondition(QFilterCriteria condition, Map<String, Object> context)
|
||||
{
|
||||
try
|
||||
{
|
||||
Object value = context.get(condition.getFieldName());
|
||||
return (BackendQueryFilterUtils.doesCriteriaMatch(condition, condition.getFieldName(), (Serializable) value));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error evaluating condition: " + condition, e);
|
||||
return (false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,339 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.AbstractHTMLWidgetRenderer;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
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.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.nocode.WidgetCount;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class NoCodeWidgetVelocityUtils
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(NoCodeWidgetVelocityUtils.class);
|
||||
|
||||
private Map<String, Object> context;
|
||||
private RenderWidgetInput input;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public NoCodeWidgetVelocityUtils(Map<String, Object> context, RenderWidgetInput input)
|
||||
{
|
||||
this.context = context;
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String helpIcon()
|
||||
{
|
||||
return ("""
|
||||
<span class="material-icons-round notranslate MuiIcon-root MuiIcon-fontSizeInherit" style="color: blue; position: relative; top: 6px;" aria-hidden="true">help_outline</span>
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String errorIcon()
|
||||
{
|
||||
return ("""
|
||||
<span class="material-icons-round notranslate MuiIcon-root MuiIcon-fontSizeInherit" style="color: red; position: relative; top: 6px;" aria-hidden="true">error_outline</span>
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String warningIcon()
|
||||
{
|
||||
return ("""
|
||||
<span class="material-icons-round notranslate MuiIcon-root MuiIcon-fontSizeInherit" style="color: orange; position: relative; top: 6px;" aria-hidden="true">warning</span>
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String checkIcon()
|
||||
{
|
||||
return ("""
|
||||
<span class="material-icons-round notranslate MuiIcon-root MuiIcon-fontSizeInherit" style="color: green; position: relative; top: 6px;" aria-hidden="true">check</span>
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String pendingIcon()
|
||||
{
|
||||
return ("""
|
||||
<span class="material-icons-round notranslate MuiIcon-root MuiIcon-fontSizeInherit" style="color: #0062ff; position: relative; top: 6px;" aria-hidden="true">pending</span>
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String spanColorGreen()
|
||||
{
|
||||
return ("""
|
||||
<span style="color: green;">
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String spanColorOrange()
|
||||
{
|
||||
return ("""
|
||||
<span style="color: orange;">
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String spanColorRed()
|
||||
{
|
||||
return ("""
|
||||
<span style="color: red;">
|
||||
""");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String plural(Integer size, String ifOne, String ifNotOne)
|
||||
{
|
||||
return StringUtils.plural(size, ifOne, ifNotOne);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String formatDateTime(Instant i)
|
||||
{
|
||||
if(i == null)
|
||||
{
|
||||
return ("");
|
||||
}
|
||||
return QValueFormatter.formatDateTimeWithZone(i.atZone(ZoneId.of(QContext.getQInstance().getDefaultTimeZoneId())));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String formatSecondsAsDuration(Integer seconds)
|
||||
{
|
||||
StringBuilder rs = new StringBuilder();
|
||||
|
||||
if(seconds == null)
|
||||
{
|
||||
return ("");
|
||||
}
|
||||
|
||||
int secondsPerDay = 24 * 60 * 60;
|
||||
if(seconds >= secondsPerDay)
|
||||
{
|
||||
int days = seconds / (secondsPerDay);
|
||||
seconds = seconds % secondsPerDay;
|
||||
rs.append(days).append(StringUtils.plural(days, " day", " days")).append(" ");
|
||||
}
|
||||
|
||||
int secondsPerHour = 60 * 60;
|
||||
if(seconds >= secondsPerHour)
|
||||
{
|
||||
int hours = seconds / (secondsPerHour);
|
||||
seconds = seconds % secondsPerHour;
|
||||
rs.append(hours).append(StringUtils.plural(hours, " hour", " hours")).append(" ");
|
||||
}
|
||||
|
||||
int secondsPerMinute = 60;
|
||||
if(seconds >= secondsPerMinute)
|
||||
{
|
||||
int minutes = seconds / (secondsPerMinute);
|
||||
seconds = seconds % secondsPerMinute;
|
||||
rs.append(minutes).append(StringUtils.plural(minutes, " minute", " minutes")).append(" ");
|
||||
}
|
||||
|
||||
if(seconds > 0 || rs.length() == 0)
|
||||
{
|
||||
rs.append(seconds).append(StringUtils.plural(seconds, " second", " seconds")).append(" ");
|
||||
}
|
||||
|
||||
if(rs.length() > 0)
|
||||
{
|
||||
rs.deleteCharAt(rs.length() - 1);
|
||||
}
|
||||
|
||||
return (rs.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String formatSecondsAsRoundedDuration(Integer seconds)
|
||||
{
|
||||
StringBuilder rs = new StringBuilder();
|
||||
|
||||
if(seconds == null)
|
||||
{
|
||||
return ("");
|
||||
}
|
||||
|
||||
int secondsPerDay = 24 * 60 * 60;
|
||||
if(seconds >= secondsPerDay)
|
||||
{
|
||||
int days = seconds / (secondsPerDay);
|
||||
return (days + StringUtils.plural(days, " day", " days"));
|
||||
}
|
||||
|
||||
int secondsPerHour = 60 * 60;
|
||||
if(seconds >= secondsPerHour)
|
||||
{
|
||||
int hours = seconds / (secondsPerHour);
|
||||
return (hours + StringUtils.plural(hours, " hour", " hours"));
|
||||
}
|
||||
|
||||
int secondsPerMinute = 60;
|
||||
if(seconds >= secondsPerMinute)
|
||||
{
|
||||
int minutes = seconds / (secondsPerMinute);
|
||||
return (minutes + StringUtils.plural(minutes, " minute", " minutes"));
|
||||
}
|
||||
|
||||
if(seconds > 0 || rs.length() == 0)
|
||||
{
|
||||
return (seconds + StringUtils.plural(seconds, " second", " seconds"));
|
||||
}
|
||||
|
||||
return ("");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String tableCountFilterLink(String countVariableName, String singular, String plural) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
WidgetCount widgetCount = (WidgetCount) context.get(countVariableName + ".source");
|
||||
Integer count = ValueUtils.getValueAsInteger(context.get(countVariableName));
|
||||
QQueryFilter filter = widgetCount.getEffectiveFilter(input);
|
||||
return (AbstractHTMLWidgetRenderer.aHrefTableFilterNoOfRecords(null, widgetCount.getTableName(), filter, count, singular, plural));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error rendering widget link", e);
|
||||
return ("");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String format(String displayFormat, Serializable value)
|
||||
{
|
||||
return (QValueFormatter.formatValue(displayFormat, value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String round(BigDecimal input, int digits)
|
||||
{
|
||||
return String.valueOf(input.setScale(digits, RoundingMode.HALF_UP));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Object ifElse(Object ifObject, Object elseObject)
|
||||
{
|
||||
if(StringUtils.hasContent(ValueUtils.getValueAsString(ifObject)))
|
||||
{
|
||||
return (ifObject);
|
||||
}
|
||||
else if(StringUtils.hasContent(ValueUtils.getValueAsString(elseObject)))
|
||||
{
|
||||
return (elseObject);
|
||||
}
|
||||
|
||||
return ("");
|
||||
}
|
||||
|
||||
}
|
@ -23,11 +23,10 @@ package com.kingsrook.qqq.backend.core.actions.permissions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPermissionRules;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -35,7 +34,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class BulkTableActionProcessPermissionChecker implements CustomPermissionChecker
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(BulkTableActionProcessPermissionChecker.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(BulkTableActionProcessPermissionChecker.class);
|
||||
|
||||
|
||||
|
||||
@ -52,8 +51,7 @@ public class BulkTableActionProcessPermissionChecker implements CustomPermission
|
||||
String tableName = parts[0];
|
||||
String bulkActionName = parts[1];
|
||||
|
||||
AbstractTableActionInput tableActionInput = new AbstractTableActionInput(actionInput.getInstance());
|
||||
tableActionInput.setSession(actionInput.getSession());
|
||||
AbstractTableActionInput tableActionInput = new AbstractTableActionInput();
|
||||
tableActionInput.setTableName(tableName);
|
||||
|
||||
switch(bulkActionName)
|
||||
|
@ -29,7 +29,9 @@ import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QPermissionDeniedException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
@ -44,8 +46,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -53,7 +53,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class PermissionsHelper
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(PermissionsHelper.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(PermissionsHelper.class);
|
||||
|
||||
|
||||
|
||||
@ -73,9 +73,22 @@ public class PermissionsHelper
|
||||
private static void checkTablePermissionThrowing(AbstractActionInput actionInput, String tableName, TablePermissionSubType permissionSubType) throws QPermissionDeniedException
|
||||
{
|
||||
warnAboutPermissionSubTypeForTables(permissionSubType);
|
||||
QTableMetaData table = actionInput.getInstance().getTable(tableName);
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(table, actionInput.getInstance()), permissionSubType, table.getName(), actionInput);
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(table, QContext.getQInstance()), permissionSubType, table.getName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String getTablePermissionName(String tableName, TablePermissionSubType permissionSubType)
|
||||
{
|
||||
QInstance qInstance = QContext.getQInstance();
|
||||
QPermissionRules rules = getEffectivePermissionRules(qInstance.getTable(tableName), qInstance);
|
||||
String permissionBaseName = getEffectivePermissionBaseName(rules, tableName);
|
||||
return (getPermissionName(permissionBaseName, permissionSubType));
|
||||
}
|
||||
|
||||
|
||||
@ -103,7 +116,7 @@ public class PermissionsHelper
|
||||
*******************************************************************************/
|
||||
public static PermissionCheckResult getPermissionCheckResult(AbstractActionInput actionInput, MetaDataWithPermissionRules metaDataWithPermissionRules)
|
||||
{
|
||||
QPermissionRules rules = getEffectivePermissionRules(metaDataWithPermissionRules, actionInput.getInstance());
|
||||
QPermissionRules rules = getEffectivePermissionRules(metaDataWithPermissionRules, QContext.getQInstance());
|
||||
String permissionBaseName = getEffectivePermissionBaseName(rules, metaDataWithPermissionRules.getName());
|
||||
|
||||
switch(rules.getLevel())
|
||||
@ -168,8 +181,8 @@ public class PermissionsHelper
|
||||
*******************************************************************************/
|
||||
public static void checkProcessPermissionThrowing(AbstractActionInput actionInput, String processName, Map<String, Serializable> processValues) throws QPermissionDeniedException
|
||||
{
|
||||
QProcessMetaData process = actionInput.getInstance().getProcess(processName);
|
||||
QPermissionRules effectivePermissionRules = getEffectivePermissionRules(process, actionInput.getInstance());
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
QPermissionRules effectivePermissionRules = getEffectivePermissionRules(process, QContext.getQInstance());
|
||||
|
||||
if(effectivePermissionRules.getCustomPermissionChecker() != null)
|
||||
{
|
||||
@ -181,7 +194,7 @@ public class PermissionsHelper
|
||||
return;
|
||||
}
|
||||
|
||||
commonCheckPermissionThrowing(effectivePermissionRules, PrivatePermissionSubType.HAS_ACCESS, process.getName(), actionInput);
|
||||
commonCheckPermissionThrowing(effectivePermissionRules, PrivatePermissionSubType.HAS_ACCESS, process.getName());
|
||||
}
|
||||
|
||||
|
||||
@ -209,8 +222,8 @@ public class PermissionsHelper
|
||||
*******************************************************************************/
|
||||
public static void checkAppPermissionThrowing(AbstractActionInput actionInput, String appName) throws QPermissionDeniedException
|
||||
{
|
||||
QAppMetaData app = actionInput.getInstance().getApp(appName);
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(app, actionInput.getInstance()), PrivatePermissionSubType.HAS_ACCESS, app.getName(), actionInput);
|
||||
QAppMetaData app = QContext.getQInstance().getApp(appName);
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(app, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, app.getName());
|
||||
}
|
||||
|
||||
|
||||
@ -238,8 +251,8 @@ public class PermissionsHelper
|
||||
*******************************************************************************/
|
||||
public static void checkReportPermissionThrowing(AbstractActionInput actionInput, String reportName) throws QPermissionDeniedException
|
||||
{
|
||||
QReportMetaData report = actionInput.getInstance().getReport(reportName);
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(report, actionInput.getInstance()), PrivatePermissionSubType.HAS_ACCESS, report.getName(), actionInput);
|
||||
QReportMetaData report = QContext.getQInstance().getReport(reportName);
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(report, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, report.getName());
|
||||
}
|
||||
|
||||
|
||||
@ -267,8 +280,8 @@ public class PermissionsHelper
|
||||
*******************************************************************************/
|
||||
public static void checkWidgetPermissionThrowing(AbstractActionInput actionInput, String widgetName) throws QPermissionDeniedException
|
||||
{
|
||||
QWidgetMetaDataInterface widget = actionInput.getInstance().getWidget(widgetName);
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(widget, actionInput.getInstance()), PrivatePermissionSubType.HAS_ACCESS, widget.getName(), actionInput);
|
||||
QWidgetMetaDataInterface widget = QContext.getQInstance().getWidget(widgetName);
|
||||
commonCheckPermissionThrowing(getEffectivePermissionRules(widget, QContext.getQInstance()), PrivatePermissionSubType.HAS_ACCESS, widget.getName());
|
||||
}
|
||||
|
||||
|
||||
@ -387,6 +400,11 @@ public class PermissionsHelper
|
||||
*******************************************************************************/
|
||||
public static QPermissionRules getEffectivePermissionRules(MetaDataWithPermissionRules metaDataWithPermissionRules, QInstance instance)
|
||||
{
|
||||
if(metaDataWithPermissionRules.getPermissionRules() == null)
|
||||
{
|
||||
LOG.warn("Null permission rules on meta data object [" + metaDataWithPermissionRules.getClass().getSimpleName() + "][" + metaDataWithPermissionRules.getName() + "] - does the instance need enriched? Returning instance default rules.");
|
||||
return (instance.getDefaultPermissionRules());
|
||||
}
|
||||
return (metaDataWithPermissionRules.getPermissionRules());
|
||||
}
|
||||
|
||||
@ -431,7 +449,7 @@ public class PermissionsHelper
|
||||
}
|
||||
}
|
||||
|
||||
if(hasPermission(actionInput.getSession(), permissionBaseName, effectivePermissionSubType))
|
||||
if(hasPermission(QContext.getQSession(), permissionBaseName, effectivePermissionSubType))
|
||||
{
|
||||
return (PermissionCheckResult.ALLOW);
|
||||
}
|
||||
@ -519,7 +537,7 @@ public class PermissionsHelper
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void commonCheckPermissionThrowing(QPermissionRules rules, PermissionSubType permissionSubType, String name, AbstractActionInput actionInput) throws QPermissionDeniedException
|
||||
private static void commonCheckPermissionThrowing(QPermissionRules rules, PermissionSubType permissionSubType, String name) throws QPermissionDeniedException
|
||||
{
|
||||
PermissionSubType effectivePermissionSubType = getEffectivePermissionSubType(rules, permissionSubType);
|
||||
String permissionBaseName = getEffectivePermissionBaseName(rules, name);
|
||||
@ -529,9 +547,9 @@ public class PermissionsHelper
|
||||
return;
|
||||
}
|
||||
|
||||
if(!hasPermission(actionInput.getSession(), permissionBaseName, effectivePermissionSubType))
|
||||
if(!hasPermission(QContext.getQSession(), permissionBaseName, effectivePermissionSubType))
|
||||
{
|
||||
// LOG.debug("Throwing permission denied for: " + getPermissionName(permissionBaseName, effectivePermissionSubType) + " for " + actionInput.getSession().getUser());
|
||||
// LOG.debug("Throwing permission denied for: " + getPermissionName(permissionBaseName, effectivePermissionSubType) + " for " + QContext.getQSession().getUser());
|
||||
throw (new QPermissionDeniedException("Permission denied."));
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
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.QueryInput;
|
||||
@ -42,8 +43,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMet
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -52,7 +51,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class RunBackendStepAction
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(RunBackendStepAction.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(RunBackendStepAction.class);
|
||||
|
||||
|
||||
|
||||
@ -107,7 +106,7 @@ public class RunBackendStepAction
|
||||
return;
|
||||
}
|
||||
|
||||
List<QFieldMetaData> fieldsToGet = new ArrayList<>();
|
||||
List<QFieldMetaData> fieldsToGet = new ArrayList<>();
|
||||
List<QFieldMetaData> requiredFieldsMissing = new ArrayList<>();
|
||||
for(QFieldMetaData field : inputMetaData.getFieldList())
|
||||
{
|
||||
@ -175,8 +174,7 @@ public class RunBackendStepAction
|
||||
{
|
||||
if(CollectionUtils.nullSafeIsEmpty(runBackendStepInput.getRecords()))
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(runBackendStepInput.getInstance());
|
||||
queryInput.setSession(runBackendStepInput.getSession());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(inputMetaData.getRecordListMetaData().getTableName());
|
||||
|
||||
// todo - handle this being async (e.g., http)
|
||||
|
@ -31,10 +31,12 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.NoCodeWidgetRenderer;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
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.RunBackendStepInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
|
||||
@ -48,7 +50,10 @@ 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.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.NoCodeWidgetFrontendComponentMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
@ -58,10 +63,9 @@ 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.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -70,7 +74,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class RunProcessAction
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(RunProcessAction.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(RunProcessAction.class);
|
||||
|
||||
public static final String BASEPULL_THIS_RUNTIME_KEY = "basepullThisRuntimeKey";
|
||||
public static final String BASEPULL_LAST_RUNTIME_KEY = "basepullLastRuntimeKey";
|
||||
@ -143,7 +147,7 @@ public class RunProcessAction
|
||||
QStepMetaData step = stepList.get(0);
|
||||
lastStepName = step.getName();
|
||||
|
||||
if(step instanceof QFrontendStepMetaData)
|
||||
if(step instanceof QFrontendStepMetaData frontendStep)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////
|
||||
// Handle what to do with frontend steps, per request setting //
|
||||
@ -153,6 +157,8 @@ public class RunProcessAction
|
||||
case BREAK ->
|
||||
{
|
||||
LOG.trace("Breaking process [" + process.getName() + "] at frontend step (as requested by caller): " + step.getName());
|
||||
processFrontendStepFieldDefaultValues(processState, frontendStep);
|
||||
processFrontendComponents(processState, frontendStep);
|
||||
processState.setNextStepName(step.getName());
|
||||
break STEP_LOOP;
|
||||
}
|
||||
@ -230,6 +236,42 @@ public class RunProcessAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void processFrontendComponents(ProcessState processState, QFrontendStepMetaData frontendStep) throws QException
|
||||
{
|
||||
for(QFrontendComponentMetaData component : CollectionUtils.nonNullList(frontendStep.getComponents()))
|
||||
{
|
||||
if(component instanceof NoCodeWidgetFrontendComponentMetaData noCodeWidgetComponent)
|
||||
{
|
||||
NoCodeWidgetRenderer noCodeWidgetRenderer = new NoCodeWidgetRenderer();
|
||||
Map<String, Object> context = noCodeWidgetRenderer.initContext(null);
|
||||
context.putAll(processState.getValues());
|
||||
String html = noCodeWidgetRenderer.renderOutputs(context, noCodeWidgetComponent.getOutputs());
|
||||
processState.getValues().put(frontendStep.getName() + ".html", html);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void processFrontendStepFieldDefaultValues(ProcessState processState, QFrontendStepMetaData step)
|
||||
{
|
||||
for(QFieldMetaData formField : CollectionUtils.mergeLists(step.getFormFields(), step.getInputFields(), step.getViewFields(), step.getOutputFields()))
|
||||
{
|
||||
if(formField.getDefaultValue() != null && processState.getValues().get(formField.getName()) == null)
|
||||
{
|
||||
processState.getValues().put(formField.getName(), formField.getDefaultValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** When we start running a process (or resuming it), get data in the RunProcessRequest
|
||||
** either from the state provider (if they're found, for a resume).
|
||||
@ -295,15 +337,25 @@ public class RunProcessAction
|
||||
*******************************************************************************/
|
||||
private void runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, QProcessMetaData qProcessMetaData, ProcessState processState) throws Exception
|
||||
{
|
||||
RunBackendStepInput runBackendStepInput = new RunBackendStepInput(runProcessInput.getInstance(), processState);
|
||||
RunBackendStepInput runBackendStepInput = new RunBackendStepInput(processState);
|
||||
runBackendStepInput.setProcessName(process.getName());
|
||||
runBackendStepInput.setStepName(backendStep.getName());
|
||||
runBackendStepInput.setTableName(process.getTableName());
|
||||
runBackendStepInput.setSession(runProcessInput.getSession());
|
||||
runBackendStepInput.setCallback(runProcessInput.getCallback());
|
||||
runBackendStepInput.setFrontendStepBehavior(runProcessInput.getFrontendStepBehavior());
|
||||
runBackendStepInput.setAsyncJobCallback(runProcessInput.getAsyncJobCallback());
|
||||
|
||||
runBackendStepInput.setTableName(process.getTableName());
|
||||
if(!StringUtils.hasContent(runBackendStepInput.getTableName()))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////
|
||||
// help support generic (e.g., not tied-to-a-table) processes //
|
||||
////////////////////////////////////////////////////////////////
|
||||
if(runProcessInput.getValue("tableName") != null)
|
||||
{
|
||||
runBackendStepInput.setTableName(ValueUtils.getValueAsString(runProcessInput.getValue("tableName")));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// if 'basepull' values are in the inputs, add to step input //
|
||||
///////////////////////////////////////////////////////////////
|
||||
@ -444,8 +496,7 @@ public class RunProcessAction
|
||||
///////////////////////////////////////
|
||||
// get the stored basepull timestamp //
|
||||
///////////////////////////////////////
|
||||
QueryInput queryInput = new QueryInput(runProcessInput.getInstance());
|
||||
queryInput.setSession(runProcessInput.getSession());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(basepullTableName);
|
||||
queryInput.setFilter(new QQueryFilter().withCriteria(
|
||||
new QFilterCriteria()
|
||||
@ -473,8 +524,7 @@ public class RunProcessAction
|
||||
////////////
|
||||
// update //
|
||||
////////////
|
||||
UpdateInput updateInput = new UpdateInput(runProcessInput.getInstance());
|
||||
updateInput.setSession(runProcessInput.getSession());
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(basepullTableName);
|
||||
updateInput.setRecords(List.of(basepullRecord));
|
||||
new UpdateAction().execute(updateInput);
|
||||
@ -488,8 +538,7 @@ public class RunProcessAction
|
||||
////////////////////////////////
|
||||
// insert new basepull record //
|
||||
////////////////////////////////
|
||||
InsertInput insertInput = new InsertInput(runProcessInput.getInstance());
|
||||
insertInput.setSession(runProcessInput.getSession());
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(basepullTableName);
|
||||
insertInput.setRecords(List.of(basepullRecord));
|
||||
new InsertAction().execute(insertInput);
|
||||
@ -527,8 +576,7 @@ public class RunProcessAction
|
||||
///////////////////////////////////////
|
||||
// get the stored basepull timestamp //
|
||||
///////////////////////////////////////
|
||||
QueryInput queryInput = new QueryInput(runProcessInput.getInstance());
|
||||
queryInput.setSession(runProcessInput.getSession());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(basepullTableName);
|
||||
queryInput.setFilter(new QQueryFilter().withCriteria(
|
||||
new QFilterCriteria()
|
||||
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.queues;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import com.amazonaws.auth.AWSStaticCredentialsProvider;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.services.sqs.AmazonSQS;
|
||||
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
|
||||
import com.amazonaws.services.sqs.model.GetQueueAttributesResult;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
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.SQSQueueProviderMetaData;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class GetQueueSize
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(GetQueueSize.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getQueueSize(QQueueProviderMetaData queueProviderMetaData, QQueueMetaData queueMetaData) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////
|
||||
// todo - handle other queue provider types, somewhere, somehow //
|
||||
//////////////////////////////////////////////////////////////////
|
||||
SQSQueueProviderMetaData queueProvider = (SQSQueueProviderMetaData) queueProviderMetaData;
|
||||
|
||||
BasicAWSCredentials credentials = new BasicAWSCredentials(queueProvider.getAccessKey(), queueProvider.getSecretKey());
|
||||
final AmazonSQS sqs = AmazonSQSClientBuilder.standard()
|
||||
.withRegion(queueProvider.getRegion())
|
||||
.withCredentials(new AWSStaticCredentialsProvider(credentials))
|
||||
.build();
|
||||
|
||||
String queueUrl = queueProvider.getBaseURL();
|
||||
if(!queueUrl.endsWith("/"))
|
||||
{
|
||||
queueUrl += "/";
|
||||
}
|
||||
queueUrl += queueMetaData.getQueueName();
|
||||
|
||||
GetQueueAttributesResult queueAttributes = sqs.getQueueAttributes(queueUrl, List.of("ApproximateNumberOfMessages"));
|
||||
String approximateNumberOfMessages = queueAttributes.getAttributes().get("ApproximateNumberOfMessages");
|
||||
return (Integer.parseInt(approximateNumberOfMessages));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error getting queue size", e, logPair("queueName", queueMetaData == null ? "null" : queueMetaData.getName()));
|
||||
throw (new QException("Error getting queue size", e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -35,15 +35,14 @@ import com.amazonaws.services.sqs.model.Message;
|
||||
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
|
||||
import com.amazonaws.services.sqs.model.ReceiveMessageResult;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
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.SQSQueueProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.StandardScheduledExecutor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -51,7 +50,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class SQSQueuePoller implements Runnable
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(SQSQueuePoller.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(SQSQueuePoller.class);
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// todo - move these 2 to a "QBaseRunnable"? //
|
||||
@ -70,8 +69,10 @@ public class SQSQueuePoller implements Runnable
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
QContext.init(qInstance, sessionSupplier.get());
|
||||
|
||||
String originalThreadName = Thread.currentThread().getName();
|
||||
Thread.currentThread().setName("SQSPoller>" + queueMetaData.getName() + StandardScheduledExecutor.newThreadNameRandomSuffix());
|
||||
Thread.currentThread().setName("SQSPoller>" + queueMetaData.getName());
|
||||
LOG.debug("Running " + this.getClass().getSimpleName() + "[" + queueMetaData.getName() + "]");
|
||||
|
||||
try
|
||||
@ -125,12 +126,13 @@ public class SQSQueuePoller implements Runnable
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
try
|
||||
{
|
||||
RunProcessInput runProcessInput = new RunProcessInput(qInstance);
|
||||
runProcessInput.setSession(sessionSupplier.get());
|
||||
RunProcessInput runProcessInput = new RunProcessInput();
|
||||
runProcessInput.setProcessName(queueMetaData.getProcessName());
|
||||
runProcessInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
|
||||
runProcessInput.addValue("bodies", bodies);
|
||||
|
||||
QContext.pushAction(runProcessInput);
|
||||
|
||||
RunProcessAction runProcessAction = new RunProcessAction();
|
||||
RunProcessOutput runProcessOutput = runProcessAction.execute(runProcessInput);
|
||||
|
||||
@ -156,6 +158,10 @@ public class SQSQueuePoller implements Runnable
|
||||
{
|
||||
LOG.warn("Error receiving SQS Messages.", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
QContext.popAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
@ -165,6 +171,7 @@ public class SQSQueuePoller implements Runnable
|
||||
finally
|
||||
{
|
||||
Thread.currentThread().setName(originalThreadName);
|
||||
QContext.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,13 +27,12 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.adapters.QRecordToCsvAdapter;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||
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.data.QRecord;
|
||||
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.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -41,7 +40,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class CsvExportStreamer implements ExportStreamerInterface
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(CsvExportStreamer.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(CsvExportStreamer.class);
|
||||
|
||||
private final QRecordToCsvAdapter qRecordToCsvAdapter;
|
||||
|
||||
|
@ -37,6 +37,7 @@ import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.excelformatting.ExcelStylerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.excelformatting.PlainExcelStyler;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||
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.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
@ -45,8 +46,6 @@ 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.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.dhatim.fastexcel.StyleSetter;
|
||||
import org.dhatim.fastexcel.Workbook;
|
||||
import org.dhatim.fastexcel.Worksheet;
|
||||
@ -57,7 +56,7 @@ import org.dhatim.fastexcel.Worksheet;
|
||||
*******************************************************************************/
|
||||
public class ExcelExportStreamer implements ExportStreamerInterface
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(ExcelExportStreamer.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(ExcelExportStreamer.class);
|
||||
|
||||
private ExportInput exportInput;
|
||||
private QTableMetaData table;
|
||||
|
@ -35,6 +35,7 @@ import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
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;
|
||||
@ -50,8 +51,6 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -67,7 +66,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class ExportAction
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(ExportAction.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(ExportAction.class);
|
||||
|
||||
private boolean preExecuteRan = false;
|
||||
private Integer countFromPreExecute = null;
|
||||
@ -149,8 +148,7 @@ public class ExportAction
|
||||
// set up a query input //
|
||||
//////////////////////////
|
||||
QueryAction queryAction = new QueryAction();
|
||||
QueryInput queryInput = new QueryInput(exportInput.getInstance());
|
||||
queryInput.setSession(exportInput.getSession());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(exportInput.getTableName());
|
||||
queryInput.setFilter(exportInput.getQueryFilter());
|
||||
queryInput.setLimit(exportInput.getLimit());
|
||||
@ -350,8 +348,7 @@ public class ExportAction
|
||||
if(exportInput.getLimit() == null || exportInput.getLimit() > reportFormat.getMaxRows())
|
||||
{
|
||||
CountInterface countInterface = backendModule.getCountInterface();
|
||||
CountInput countInput = new CountInput(exportInput.getInstance());
|
||||
countInput.setSession(exportInput.getSession());
|
||||
CountInput countInput = new CountInput();
|
||||
countInput.setTableName(exportInput.getTableName());
|
||||
countInput.setFilter(exportInput.getQueryFilter());
|
||||
CountOutput countOutput = countInterface.execute(countInput);
|
||||
|
@ -185,7 +185,7 @@ public class FormulaInterpreter
|
||||
case "DIVIDE":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 2, variableInterpreter);
|
||||
if(numbers.get(1) == null || numbers.get(1).equals(BigDecimal.ZERO))
|
||||
if(numbers.get(1) == null || numbers.get(1).compareTo(BigDecimal.ZERO) == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -194,7 +194,7 @@ public class FormulaInterpreter
|
||||
case "DIVIDE_SCALE":
|
||||
{
|
||||
List<BigDecimal> numbers = getNumberArgumentList(args, 3, variableInterpreter);
|
||||
if(numbers.get(1) == null || numbers.get(1).equals(BigDecimal.ZERO))
|
||||
if(numbers.get(1) == null || numbers.get(1).compareTo(BigDecimal.ZERO) == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
@ -45,13 +45,13 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QFormulaException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
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.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.tables.query.JoinsContext;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
@ -65,15 +65,14 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.ReportType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractTransformStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.BackendStepPostRunInput;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.BackendStepPostRunOutput;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.aggregates.AggregatesInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.aggregates.BigDecimalAggregates;
|
||||
import com.kingsrook.qqq.backend.core.utils.aggregates.IntegerAggregates;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -90,7 +89,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class GenerateReportAction
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(GenerateReportAction.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(GenerateReportAction.class);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// summaryAggregates and varianceAggregates are multi-level maps, ala: //
|
||||
@ -216,8 +215,7 @@ public class GenerateReportAction
|
||||
QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter();
|
||||
variableInterpreter.addValueMap("input", reportInput.getInputValues());
|
||||
|
||||
ExportInput exportInput = new ExportInput(reportInput.getInstance());
|
||||
exportInput.setSession(reportInput.getSession());
|
||||
ExportInput exportInput = new ExportInput();
|
||||
exportInput.setReportFormat(reportFormat);
|
||||
exportInput.setFilename(reportInput.getFilename());
|
||||
exportInput.setTitleRow(getTitle(reportView, variableInterpreter));
|
||||
@ -227,7 +225,7 @@ public class GenerateReportAction
|
||||
JoinsContext joinsContext = null;
|
||||
if(StringUtils.hasContent(dataSource.getSourceTable()))
|
||||
{
|
||||
joinsContext = new JoinsContext(exportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins());
|
||||
joinsContext = new JoinsContext(exportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), dataSource.getQueryFilter());
|
||||
}
|
||||
|
||||
List<QFieldMetaData> fields = new ArrayList<>();
|
||||
@ -277,8 +275,7 @@ public class GenerateReportAction
|
||||
{
|
||||
transformStep = QCodeLoader.getBackendStep(AbstractTransformStep.class, tableView.getRecordTransformStep());
|
||||
|
||||
transformStepInput = new RunBackendStepInput(reportInput.getInstance());
|
||||
transformStepInput.setSession(reportInput.getSession());
|
||||
transformStepInput = new RunBackendStepInput();
|
||||
transformStepInput.setValues(reportInput.getInputValues());
|
||||
|
||||
transformStepOutput = new RunBackendStepOutput();
|
||||
@ -304,15 +301,14 @@ public class GenerateReportAction
|
||||
QQueryFilter queryFilter = dataSource.getQueryFilter() == null ? new QQueryFilter() : dataSource.getQueryFilter().clone();
|
||||
setInputValuesInQueryFilter(reportInput, queryFilter);
|
||||
|
||||
QueryInput queryInput = new QueryInput(reportInput.getInstance());
|
||||
queryInput.setSession(reportInput.getSession());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setRecordPipe(recordPipe);
|
||||
queryInput.setTableName(dataSource.getSourceTable());
|
||||
queryInput.setFilter(queryFilter);
|
||||
queryInput.setQueryJoins(dataSource.getQueryJoins());
|
||||
|
||||
queryInput.setShouldTranslatePossibleValues(true);
|
||||
queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource, new JoinsContext(reportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins())));
|
||||
queryInput.setFieldsToTranslatePossibleValues(setupFieldsToTranslatePossibleValues(reportInput, dataSource, new JoinsContext(reportInput.getInstance(), dataSource.getSourceTable(), dataSource.getQueryJoins(), queryInput.getFilter())));
|
||||
|
||||
if(dataSource.getQueryInputCustomizer() != null)
|
||||
{
|
||||
@ -361,7 +357,7 @@ public class GenerateReportAction
|
||||
////////////////////////////////////////////////
|
||||
if(transformStep != null)
|
||||
{
|
||||
transformStep.postRun(transformStepInput, transformStepOutput);
|
||||
transformStep.postRun(new BackendStepPostRunInput(transformStepInput), new BackendStepPostRunOutput(transformStepOutput));
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,23 +412,7 @@ public class GenerateReportAction
|
||||
return;
|
||||
}
|
||||
|
||||
QMetaDataVariableInterpreter variableInterpreter = new QMetaDataVariableInterpreter();
|
||||
variableInterpreter.addValueMap("input", reportInput.getInputValues());
|
||||
for(QFilterCriteria criterion : queryFilter.getCriteria())
|
||||
{
|
||||
if(criterion.getValues() != null)
|
||||
{
|
||||
List<Serializable> newValues = new ArrayList<>();
|
||||
|
||||
for(Serializable value : criterion.getValues())
|
||||
{
|
||||
String valueAsString = ValueUtils.getValueAsString(value);
|
||||
Serializable interpretedValue = variableInterpreter.interpretForObject(valueAsString);
|
||||
newValues.add(interpretedValue);
|
||||
}
|
||||
criterion.setValues(newValues);
|
||||
}
|
||||
}
|
||||
queryFilter.interpretValues(reportInput.getInputValues());
|
||||
}
|
||||
|
||||
|
||||
@ -597,8 +577,7 @@ public class GenerateReportAction
|
||||
QTableMetaData table = reportInput.getInstance().getTable(dataSource.getSourceTable());
|
||||
SummaryOutput summaryOutput = computeSummaryRowsForView(reportInput, view, table);
|
||||
|
||||
ExportInput exportInput = new ExportInput(reportInput.getInstance());
|
||||
exportInput.setSession(reportInput.getSession());
|
||||
ExportInput exportInput = new ExportInput();
|
||||
exportInput.setReportFormat(reportFormat);
|
||||
exportInput.setFilename(reportInput.getFilename());
|
||||
exportInput.setTitleRow(summaryOutput.titleRow);
|
||||
|
@ -30,14 +30,13 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||
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.data.QRecord;
|
||||
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.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -45,14 +44,14 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class JsonExportStreamer implements ExportStreamerInterface
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(JsonExportStreamer.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(JsonExportStreamer.class);
|
||||
|
||||
private ExportInput exportInput;
|
||||
private QTableMetaData table;
|
||||
private List<QFieldMetaData> fields;
|
||||
private OutputStream outputStream;
|
||||
|
||||
private boolean needComma = false;
|
||||
private boolean needComma = false;
|
||||
private boolean prettyPrint = true;
|
||||
|
||||
|
||||
|
@ -27,11 +27,10 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||
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.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -41,7 +40,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class ListOfMapsExportStreamer implements ExportStreamerInterface
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(ListOfMapsExportStreamer.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(ListOfMapsExportStreamer.class);
|
||||
|
||||
private ExportInput exportInput;
|
||||
private List<QFieldMetaData> fields;
|
||||
|
@ -27,10 +27,9 @@ import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -39,7 +38,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class RecordPipe
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(RecordPipe.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(RecordPipe.class);
|
||||
|
||||
private static final long BLOCKING_SLEEP_MILLIS = 100;
|
||||
private static final long MAX_SLEEP_LOOP_MILLIS = 300_000; // 5 minutes
|
||||
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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.scripts;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.ScriptExecutionLoggerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
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.scripts.ExecuteCodeInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAdHocRecordScriptInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAdHocRecordScriptOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class RunAdHocRecordScriptAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(RunAdHocRecordScriptAction.class);
|
||||
|
||||
private Map<Integer, ScriptRevision> scriptRevisionCacheByScriptRevisionId = new HashMap<>();
|
||||
private Map<Integer, ScriptRevision> scriptRevisionCacheByScriptId = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void run(RunAdHocRecordScriptInput input, RunAdHocRecordScriptOutput output) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
ActionHelper.validateSession(input);
|
||||
|
||||
/////////////////////////
|
||||
// figure out the code //
|
||||
/////////////////////////
|
||||
ScriptRevision scriptRevision = getScriptRevision(input);
|
||||
if(scriptRevision == null)
|
||||
{
|
||||
throw (new QException("Script revision was not found."));
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
// figure out the records //
|
||||
////////////////////////////
|
||||
QTableMetaData table = QContext.getQInstance().getTable(input.getTableName());
|
||||
if(CollectionUtils.nullSafeIsEmpty(input.getRecordList()))
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(input.getTableName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getRecordPrimaryKeyList())));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
input.setRecordList(queryOutput.getRecords());
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeIsEmpty(input.getRecordList()))
|
||||
{
|
||||
////////////////////////////////////////
|
||||
// just return if nothing found? idk //
|
||||
////////////////////////////////////////
|
||||
LOG.info("No records supplied as input (or found via primary keys); exiting with noop");
|
||||
return;
|
||||
}
|
||||
|
||||
/////////////
|
||||
// run it! //
|
||||
/////////////
|
||||
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput();
|
||||
executeCodeInput.setInput(new HashMap<>(Objects.requireNonNullElseGet(input.getInputValues(), HashMap::new)));
|
||||
executeCodeInput.getInput().put("records", new ArrayList<>(input.getRecordList()));
|
||||
executeCodeInput.setContext(new HashMap<>());
|
||||
if(input.getOutputObject() != null)
|
||||
{
|
||||
executeCodeInput.getContext().put("output", input.getOutputObject());
|
||||
}
|
||||
|
||||
if(input.getScriptUtils() != null)
|
||||
{
|
||||
executeCodeInput.getContext().put("scriptUtils", input.getScriptUtils());
|
||||
}
|
||||
|
||||
executeCodeInput.getContext().put("api", new ScriptApi());
|
||||
|
||||
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// let caller supply a logger, or by default use StoreScriptLogAndScriptLogLineExecutionLogger //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QCodeExecutionLoggerInterface executionLogger = Objects.requireNonNullElseGet(input.getLogger(), () -> new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId()));
|
||||
executeCodeInput.setExecutionLogger(executionLogger);
|
||||
if(executionLogger instanceof ScriptExecutionLoggerInterface scriptExecutionLoggerInterface)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if logger is aware of scripts (as opposed to a generic CodeExecution logger), give it the ids. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
scriptExecutionLoggerInterface.setScriptId(scriptRevision.getScriptId());
|
||||
scriptExecutionLoggerInterface.setScriptRevisionId(scriptRevision.getId());
|
||||
}
|
||||
|
||||
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
|
||||
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
|
||||
|
||||
output.setOutput(executeCodeOutput.getOutput());
|
||||
output.setLogger(executionLogger);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
output.setException(Optional.of(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private ScriptRevision getScriptRevision(RunAdHocRecordScriptInput input) throws QException
|
||||
{
|
||||
AdHocScriptCodeReference codeReference = input.getCodeReference();
|
||||
if(codeReference.getScriptRevisionRecord() != null)
|
||||
{
|
||||
return (new ScriptRevision(codeReference.getScriptRevisionRecord()));
|
||||
}
|
||||
|
||||
if(codeReference.getScriptRevisionId() != null)
|
||||
{
|
||||
if(!scriptRevisionCacheByScriptRevisionId.containsKey(codeReference.getScriptRevisionId()))
|
||||
{
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(ScriptRevision.TABLE_NAME);
|
||||
getInput.setPrimaryKey(codeReference.getScriptRevisionId());
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
if(getOutput.getRecord() != null)
|
||||
{
|
||||
scriptRevisionCacheByScriptRevisionId.put(codeReference.getScriptRevisionId(), new ScriptRevision(getOutput.getRecord()));
|
||||
}
|
||||
else
|
||||
{
|
||||
scriptRevisionCacheByScriptRevisionId.put(codeReference.getScriptRevisionId(), null);
|
||||
}
|
||||
}
|
||||
|
||||
return (scriptRevisionCacheByScriptRevisionId.get(codeReference.getScriptRevisionId()));
|
||||
}
|
||||
|
||||
if(codeReference.getScriptId() != null)
|
||||
{
|
||||
if(!scriptRevisionCacheByScriptId.containsKey(codeReference.getScriptId()))
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(ScriptRevision.TABLE_NAME);
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria("script.id", QCriteriaOperator.EQUALS, codeReference.getScriptId())));
|
||||
queryInput.withQueryJoin(new QueryJoin(Script.TABLE_NAME).withBaseTableOrAlias(ScriptRevision.TABLE_NAME).withJoinMetaData(QContext.getQInstance().getJoin("currentScriptRevision")));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(queryOutput.getRecords()))
|
||||
{
|
||||
scriptRevisionCacheByScriptId.put(codeReference.getScriptId(), new ScriptRevision(queryOutput.getRecords().get(0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
scriptRevisionCacheByScriptId.put(codeReference.getScriptId(), null);
|
||||
}
|
||||
}
|
||||
|
||||
return (scriptRevisionCacheByScriptId.get(codeReference.getScriptId()));
|
||||
}
|
||||
|
||||
throw (new QException("Code reference did not contain a scriptRevision, scriptRevisionId, or scriptId"));
|
||||
}
|
||||
|
||||
}
|
@ -24,7 +24,11 @@ package com.kingsrook.qqq.backend.core.actions.scripts;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.ScriptExecutionLoggerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.StoreScriptLogAndScriptLogLineExecutionLogger;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -35,6 +39,7 @@ import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptI
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.RunAssociatedScriptOutput;
|
||||
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.metadata.code.AssociatedScriptCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.scripts.Script;
|
||||
@ -46,6 +51,8 @@ import com.kingsrook.qqq.backend.core.model.scripts.ScriptRevision;
|
||||
*******************************************************************************/
|
||||
public class RunAssociatedScriptAction
|
||||
{
|
||||
private Map<AssociatedScriptCodeReference, ScriptRevision> scriptRevisionCache = new HashMap<>();
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
@ -54,32 +61,37 @@ public class RunAssociatedScriptAction
|
||||
{
|
||||
ActionHelper.validateSession(input);
|
||||
|
||||
Serializable scriptId = getScriptId(input);
|
||||
if(scriptId == null)
|
||||
{
|
||||
throw (new QNotFoundException("The input record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey()
|
||||
+ "] does not have a script specified for [" + input.getCodeReference().getFieldName() + "]"));
|
||||
}
|
||||
ScriptRevision scriptRevision = getScriptRevision(input);
|
||||
|
||||
Script script = getScript(input, scriptId);
|
||||
if(script.getCurrentScriptRevisionId() == null)
|
||||
{
|
||||
throw (new QNotFoundException("The script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey()
|
||||
+ "] (scriptId=" + scriptId + ") does not have a current version."));
|
||||
}
|
||||
|
||||
ScriptRevision scriptRevision = getCurrentScriptRevision(input, script.getCurrentScriptRevisionId());
|
||||
|
||||
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(input.getInstance());
|
||||
executeCodeInput.setSession(input.getSession());
|
||||
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput();
|
||||
executeCodeInput.setInput(new HashMap<>(input.getInputValues()));
|
||||
executeCodeInput.setContext(new HashMap<>());
|
||||
if(input.getOutputObject() != null)
|
||||
{
|
||||
executeCodeInput.getContext().put("output", input.getOutputObject());
|
||||
}
|
||||
|
||||
if(input.getScriptUtils() != null)
|
||||
{
|
||||
executeCodeInput.getContext().put("scriptUtils", input.getScriptUtils());
|
||||
}
|
||||
|
||||
executeCodeInput.setCodeReference(new QCodeReference().withInlineCode(scriptRevision.getContents()).withCodeType(QCodeType.JAVA_SCRIPT)); // todo - code type as attribute of script!!
|
||||
executeCodeInput.setExecutionLogger(new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId()));
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// let caller supply a logger, or by default use StoreScriptLogAndScriptLogLineExecutionLogger //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QCodeExecutionLoggerInterface executionLogger = Objects.requireNonNullElseGet(input.getLogger(), () -> new StoreScriptLogAndScriptLogLineExecutionLogger(scriptRevision.getScriptId(), scriptRevision.getId()));
|
||||
executeCodeInput.setExecutionLogger(executionLogger);
|
||||
if(executionLogger instanceof ScriptExecutionLoggerInterface scriptExecutionLoggerInterface)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if logger is aware of scripts (as opposed to a generic CodeExecution logger), give it the ids. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
scriptExecutionLoggerInterface.setScriptId(scriptRevision.getScriptId());
|
||||
scriptExecutionLoggerInterface.setScriptRevisionId(scriptRevision.getId());
|
||||
}
|
||||
|
||||
ExecuteCodeOutput executeCodeOutput = new ExecuteCodeOutput();
|
||||
new ExecuteCodeAction().run(executeCodeInput, executeCodeOutput);
|
||||
|
||||
@ -88,13 +100,42 @@ public class RunAssociatedScriptAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private ScriptRevision getScriptRevision(RunAssociatedScriptInput input) throws QException
|
||||
{
|
||||
if(!scriptRevisionCache.containsKey(input.getCodeReference()))
|
||||
{
|
||||
Serializable scriptId = getScriptId(input);
|
||||
if(scriptId == null)
|
||||
{
|
||||
throw (new QNotFoundException("The input record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey()
|
||||
+ "] does not have a script specified for [" + input.getCodeReference().getFieldName() + "]"));
|
||||
}
|
||||
|
||||
Script script = getScript(input, scriptId);
|
||||
if(script.getCurrentScriptRevisionId() == null)
|
||||
{
|
||||
throw (new QNotFoundException("The script for record [" + input.getCodeReference().getRecordTable() + "][" + input.getCodeReference().getRecordPrimaryKey()
|
||||
+ "] (scriptId=" + scriptId + ") does not have a current version."));
|
||||
}
|
||||
|
||||
ScriptRevision scriptRevision = getCurrentScriptRevision(input, script.getCurrentScriptRevisionId());
|
||||
scriptRevisionCache.put(input.getCodeReference(), scriptRevision);
|
||||
}
|
||||
|
||||
return scriptRevisionCache.get(input.getCodeReference());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private ScriptRevision getCurrentScriptRevision(RunAssociatedScriptInput input, Serializable scriptRevisionId) throws QException
|
||||
{
|
||||
GetInput getInput = new GetInput(input.getInstance());
|
||||
getInput.setSession(input.getSession());
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName("scriptRevision");
|
||||
getInput.setPrimaryKey(scriptRevisionId);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
@ -114,8 +155,7 @@ public class RunAssociatedScriptAction
|
||||
*******************************************************************************/
|
||||
private Script getScript(RunAssociatedScriptInput input, Serializable scriptId) throws QException
|
||||
{
|
||||
GetInput getInput = new GetInput(input.getInstance());
|
||||
getInput.setSession(input.getSession());
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName("script");
|
||||
getInput.setPrimaryKey(scriptId);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
@ -136,8 +176,7 @@ public class RunAssociatedScriptAction
|
||||
*******************************************************************************/
|
||||
private Serializable getScriptId(RunAssociatedScriptInput input) throws QException
|
||||
{
|
||||
GetInput getInput = new GetInput(input.getInstance());
|
||||
getInput.setSession(input.getSession());
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(input.getCodeReference().getRecordTable());
|
||||
getInput.setPrimaryKey(input.getCodeReference().getRecordPrimaryKey());
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
|
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.scripts;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.TablePermissionSubType;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Object made available to scripts for access to qqq api (e.g., query, insert,
|
||||
** etc, plus object constructors).
|
||||
*******************************************************************************/
|
||||
public class ScriptApi implements Serializable
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QueryInput newQueryInput()
|
||||
{
|
||||
return (new QueryInput());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QQueryFilter newQueryFilter()
|
||||
{
|
||||
return (new QQueryFilter());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFilterCriteria newFilterCriteria()
|
||||
{
|
||||
return (new QFilterCriteria());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFilterOrderBy newFilterOrderBy()
|
||||
{
|
||||
return (new QFilterOrderBy());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QRecord newRecord()
|
||||
{
|
||||
return (new QRecord());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> query(String tableName, QQueryFilter filter) throws QException
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setFilter(filter);
|
||||
PermissionsHelper.checkTablePermissionThrowing(queryInput, TablePermissionSubType.READ);
|
||||
return (new QueryAction().execute(queryInput).getRecords());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<QRecord> query(QueryInput queryInput) throws QException
|
||||
{
|
||||
PermissionsHelper.checkTablePermissionThrowing(queryInput, TablePermissionSubType.READ);
|
||||
return (new QueryAction().execute(queryInput).getRecords());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void insert(String tableName, QRecord record) throws QException
|
||||
{
|
||||
insert(tableName, List.of(record));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void insert(String tableName, List<QRecord> recordList) throws QException
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(tableName);
|
||||
insertInput.setRecords(recordList);
|
||||
PermissionsHelper.checkTablePermissionThrowing(insertInput, TablePermissionSubType.INSERT);
|
||||
new InsertAction().execute(insertInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void update(String tableName, QRecord record) throws QException
|
||||
{
|
||||
update(tableName, List.of(record));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void update(String tableName, List<QRecord> recordList) throws QException
|
||||
{
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(tableName);
|
||||
updateInput.setRecords(recordList);
|
||||
PermissionsHelper.checkTablePermissionThrowing(updateInput, TablePermissionSubType.EDIT);
|
||||
new UpdateAction().execute(updateInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void delete(String tableName, Serializable primaryKey) throws QException
|
||||
{
|
||||
delete(tableName, List.of(primaryKey));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void delete(String tableName, QRecord record) throws QException
|
||||
{
|
||||
delete(tableName, List.of(record));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void delete(String tableName, List<?> recordOrPrimaryKeyList) throws QException
|
||||
{
|
||||
QTableMetaData table = QContext.getQInstance().getTable(tableName);
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(tableName);
|
||||
List<Serializable> primaryKeyList = new ArrayList<>();
|
||||
for(Object o : recordOrPrimaryKeyList)
|
||||
{
|
||||
if(o instanceof QRecord qRecord)
|
||||
{
|
||||
primaryKeyList.add(qRecord.getValue(table.getPrimaryKeyField()));
|
||||
}
|
||||
else
|
||||
{
|
||||
primaryKeyList.add((Serializable) o);
|
||||
}
|
||||
}
|
||||
deleteInput.setPrimaryKeys(primaryKeyList);
|
||||
PermissionsHelper.checkTablePermissionThrowing(deleteInput, TablePermissionSubType.DELETE);
|
||||
new DeleteAction().execute(deleteInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void delete(String tableName, QQueryFilter filter) throws QException
|
||||
{
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(tableName);
|
||||
deleteInput.setQueryFilter(filter);
|
||||
PermissionsHelper.checkTablePermissionThrowing(deleteInput, TablePermissionSubType.DELETE);
|
||||
new DeleteAction().execute(deleteInput);
|
||||
}
|
||||
|
||||
}
|
@ -83,8 +83,7 @@ public class StoreAssociatedScriptAction
|
||||
/////////////////////////////////////////////////////////////
|
||||
QRecord associatedRecord;
|
||||
{
|
||||
GetInput getInput = new GetInput(input.getInstance());
|
||||
getInput.setSession(input.getSession());
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName(input.getTableName());
|
||||
getInput.setPrimaryKey(input.getRecordPrimaryKey());
|
||||
getInput.setShouldGenerateDisplayValues(true);
|
||||
@ -107,8 +106,7 @@ public class StoreAssociatedScriptAction
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// get the script type - that'll be part of the new script's name //
|
||||
////////////////////////////////////////////////////////////////////
|
||||
GetInput getInput = new GetInput(input.getInstance());
|
||||
getInput.setSession(input.getSession());
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName("scriptType");
|
||||
getInput.setPrimaryKey(associatedScript.getScriptTypeId());
|
||||
getInput.setShouldGenerateDisplayValues(true);
|
||||
@ -125,8 +123,7 @@ public class StoreAssociatedScriptAction
|
||||
script = new QRecord();
|
||||
script.setValue("scriptTypeId", associatedScript.getScriptTypeId());
|
||||
script.setValue("name", associatedRecord.getRecordLabel() + " - " + scriptType.getRecordLabel());
|
||||
InsertInput insertInput = new InsertInput(input.getInstance());
|
||||
insertInput.setSession(input.getSession());
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName("script");
|
||||
insertInput.setRecords(List.of(script));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
@ -135,8 +132,7 @@ public class StoreAssociatedScriptAction
|
||||
/////////////////////////////////////////////////////////////
|
||||
// update the associated record to point at the new script //
|
||||
/////////////////////////////////////////////////////////////
|
||||
UpdateInput updateInput = new UpdateInput(input.getInstance());
|
||||
updateInput.setSession(input.getSession());
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(input.getTableName());
|
||||
updateInput.setRecords(List.of(new QRecord()
|
||||
.withValue(table.getPrimaryKeyField(), associatedRecord.getValue(table.getPrimaryKeyField()))
|
||||
@ -149,15 +145,13 @@ public class StoreAssociatedScriptAction
|
||||
////////////////////////////////////////
|
||||
// get the existing script, to update //
|
||||
////////////////////////////////////////
|
||||
GetInput getInput = new GetInput(input.getInstance());
|
||||
getInput.setSession(input.getSession());
|
||||
GetInput getInput = new GetInput();
|
||||
getInput.setTableName("script");
|
||||
getInput.setPrimaryKey(existingScriptId);
|
||||
GetOutput getOutput = new GetAction().execute(getInput);
|
||||
script = getOutput.getRecord();
|
||||
|
||||
QueryInput queryInput = new QueryInput(input.getInstance());
|
||||
queryInput.setSession(input.getSession());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName("scriptRevision");
|
||||
queryInput.setFilter(new QQueryFilter()
|
||||
.withCriteria(new QFilterCriteria("scriptId", QCriteriaOperator.EQUALS, List.of(script.getValue("id"))))
|
||||
@ -202,8 +196,7 @@ public class StoreAssociatedScriptAction
|
||||
scriptRevision.setValue("author", "Unknown");
|
||||
}
|
||||
|
||||
InsertInput insertInput = new InsertInput(input.getInstance());
|
||||
insertInput.setSession(input.getSession());
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName("scriptRevision");
|
||||
insertInput.setRecords(List.of(scriptRevision));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
@ -213,8 +206,7 @@ public class StoreAssociatedScriptAction
|
||||
// update the script to point at the new revision //
|
||||
////////////////////////////////////////////////////
|
||||
script.setValue("currentScriptRevisionId", scriptRevision.getValue("id"));
|
||||
UpdateInput updateInput = new UpdateInput(input.getInstance());
|
||||
updateInput.setSession(input.getSession());
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName("script");
|
||||
updateInput.setRecords(List.of(script));
|
||||
new UpdateAction().execute(updateInput);
|
||||
|
@ -80,8 +80,7 @@ public interface TestScriptActionInterface
|
||||
*******************************************************************************/
|
||||
default void execute(TestScriptInput input, TestScriptOutput output) throws QException
|
||||
{
|
||||
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput(input.getInstance());
|
||||
executeCodeInput.setSession(input.getSession());
|
||||
ExecuteCodeInput executeCodeInput = new ExecuteCodeInput();
|
||||
executeCodeInput.setContext(new HashMap<>());
|
||||
|
||||
executeCodeInput.setCodeReference(input.getCodeReference());
|
||||
|
@ -0,0 +1,160 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.scripts.logging;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger extends BuildScriptLogAndScriptLogLineExecutionLogger implements ScriptExecutionLoggerInterface
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger.class);
|
||||
|
||||
private List<QRecord> scriptLogs = new ArrayList<>();
|
||||
private List<List<QRecord>> scriptLogLines = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AccumulatingBuildScriptLogAndScriptLogLineExecutionLogger()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void acceptExecutionStart(ExecuteCodeInput executeCodeInput)
|
||||
{
|
||||
super.acceptExecutionStart(executeCodeInput);
|
||||
super.setScriptLogLines(new ArrayList<>());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void acceptException(Exception exception)
|
||||
{
|
||||
accumulate(null, exception);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void acceptExecutionEnd(Serializable output)
|
||||
{
|
||||
accumulate(output, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void accumulate(Serializable output, Exception exception)
|
||||
{
|
||||
super.updateHeaderAtEnd(output, exception);
|
||||
scriptLogs.add(super.getScriptLog());
|
||||
scriptLogLines.add(new ArrayList<>(super.getScriptLogLines()));
|
||||
super.getScriptLogLines().clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void storeAndClear()
|
||||
{
|
||||
try
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName("scriptLog");
|
||||
insertInput.setRecords(scriptLogs);
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
|
||||
List<QRecord> flatScriptLogLines = new ArrayList<>();
|
||||
for(int i = 0; i < insertOutput.getRecords().size(); i++)
|
||||
{
|
||||
QRecord insertedScriptLog = insertOutput.getRecords().get(i);
|
||||
List<QRecord> subScriptLogLines = scriptLogLines.get(i);
|
||||
subScriptLogLines.forEach(r -> r.setValue("scriptLogId", insertedScriptLog.getValueInteger("id")));
|
||||
flatScriptLogLines.addAll(subScriptLogLines);
|
||||
}
|
||||
|
||||
insertInput = new InsertInput();
|
||||
insertInput.setTableName("scriptLogLine");
|
||||
insertInput.setRecords(flatScriptLogLines);
|
||||
new InsertAction().execute(insertInput);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error storing script logs", e);
|
||||
}
|
||||
|
||||
scriptLogs.clear();
|
||||
scriptLogLines.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setScriptId(Integer scriptId)
|
||||
{
|
||||
super.setScriptId(scriptId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setScriptRevisionId(Integer scriptRevisionId)
|
||||
{
|
||||
super.setScriptRevisionId(scriptRevisionId);
|
||||
}
|
||||
}
|
@ -27,12 +27,11 @@ import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -40,9 +39,9 @@ import org.apache.logging.log4j.Logger;
|
||||
** scriptLogLine records - but doesn't insert them. e.g., useful for testing
|
||||
** (both in junit, and for users in-app).
|
||||
*******************************************************************************/
|
||||
public class BuildScriptLogAndScriptLogLineExecutionLogger implements QCodeExecutionLoggerInterface
|
||||
public class BuildScriptLogAndScriptLogLineExecutionLogger implements QCodeExecutionLoggerInterface, ScriptExecutionLoggerInterface
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(BuildScriptLogAndScriptLogLineExecutionLogger.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(BuildScriptLogAndScriptLogLineExecutionLogger.class);
|
||||
|
||||
private QRecord scriptLog;
|
||||
private List<QRecord> scriptLogLines = new ArrayList<>();
|
||||
@ -218,4 +217,37 @@ public class BuildScriptLogAndScriptLogLineExecutionLogger implements QCodeExecu
|
||||
{
|
||||
this.scriptLog = scriptLog;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for scriptLogLines
|
||||
**
|
||||
*******************************************************************************/
|
||||
protected void setScriptLogLines(List<QRecord> scriptLogLines)
|
||||
{
|
||||
this.scriptLogLines = scriptLogLines;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setScriptId(Integer scriptId)
|
||||
{
|
||||
this.scriptId = scriptId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void setScriptRevisionId(Integer scriptRevisionId)
|
||||
{
|
||||
this.scriptRevisionId = scriptRevisionId;
|
||||
}
|
||||
}
|
||||
|
@ -24,12 +24,11 @@ package com.kingsrook.qqq.backend.core.actions.scripts.logging;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -37,7 +36,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class Log4jCodeExecutionLogger implements QCodeExecutionLoggerInterface
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(Log4jCodeExecutionLogger.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(Log4jCodeExecutionLogger.class);
|
||||
|
||||
private QCodeReference qCodeReference;
|
||||
private String uuid = UUID.randomUUID().toString();
|
||||
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.scripts.logging;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface ScriptExecutionLoggerInterface
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void setScriptId(Integer scriptId);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
void setScriptRevisionId(Integer scriptRevisionId);
|
||||
}
|
@ -26,13 +26,12 @@ import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.scripts.ExecuteCodeInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -41,7 +40,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class StoreScriptLogAndScriptLogLineExecutionLogger extends BuildScriptLogAndScriptLogLineExecutionLogger
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(StoreScriptLogAndScriptLogLineExecutionLogger.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(StoreScriptLogAndScriptLogLineExecutionLogger.class);
|
||||
|
||||
|
||||
|
||||
@ -66,8 +65,7 @@ public class StoreScriptLogAndScriptLogLineExecutionLogger extends BuildScriptLo
|
||||
{
|
||||
super.acceptExecutionStart(executeCodeInput);
|
||||
|
||||
InsertInput insertInput = new InsertInput(executeCodeInput.getInstance());
|
||||
insertInput.setSession(executeCodeInput.getSession());
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName("scriptLog");
|
||||
insertInput.setRecords(List.of(getScriptLog()));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
@ -112,16 +110,14 @@ public class StoreScriptLogAndScriptLogLineExecutionLogger extends BuildScriptLo
|
||||
try
|
||||
{
|
||||
updateHeaderAtEnd(output, exception);
|
||||
UpdateInput updateInput = new UpdateInput(executeCodeInput.getInstance());
|
||||
updateInput.setSession(executeCodeInput.getSession());
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName("scriptLog");
|
||||
updateInput.setRecords(List.of(getScriptLog()));
|
||||
new UpdateAction().execute(updateInput);
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(getScriptLogLines()))
|
||||
{
|
||||
InsertInput insertInput = new InsertInput(executeCodeInput.getInstance());
|
||||
insertInput.setSession(executeCodeInput.getSession());
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName("scriptLogLine");
|
||||
insertInput.setRecords(getScriptLogLines());
|
||||
new InsertAction().execute(insertInput);
|
||||
|
@ -24,19 +24,35 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.interfaces.DeleteInterface;
|
||||
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.audits.DMLAuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
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;
|
||||
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.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
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 org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -45,7 +61,9 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class DeleteAction
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(DeleteAction.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(DeleteAction.class);
|
||||
|
||||
public static final String NOT_FOUND_ERROR_PREFIX = "No record was found to delete";
|
||||
|
||||
|
||||
|
||||
@ -58,7 +76,6 @@ public class DeleteAction
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend());
|
||||
// todo pre-customization - just get to modify the request?
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(deleteInput.getPrimaryKeys()) && deleteInput.getQueryFilter() != null)
|
||||
{
|
||||
@ -82,10 +99,186 @@ public class DeleteAction
|
||||
}
|
||||
}
|
||||
|
||||
DeleteOutput deleteResult = deleteInterface.execute(deleteInput);
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
List<QRecord> recordListForAudit = getRecordListForAuditIfNeeded(deleteInput);
|
||||
List<QRecord> recordsWithValidationErrors = validateRecordsExistAndCanBeAccessed(deleteInput, recordListForAudit);
|
||||
|
||||
return deleteResult;
|
||||
DeleteOutput deleteOutput = deleteInterface.execute(deleteInput);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// merge the backend's output with any validation errors we found (whose ids wouldn't have gotten into the backend delete) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<QRecord> outputRecordsWithErrors = deleteOutput.getRecordsWithErrors();
|
||||
if(outputRecordsWithErrors == null)
|
||||
{
|
||||
deleteOutput.setRecordsWithErrors(new ArrayList<>());
|
||||
outputRecordsWithErrors = deleteOutput.getRecordsWithErrors();
|
||||
}
|
||||
|
||||
outputRecordsWithErrors.addAll(recordsWithValidationErrors);
|
||||
|
||||
manageAssociations(deleteInput);
|
||||
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(deleteInput).withRecordList(recordListForAudit));
|
||||
|
||||
return deleteOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void manageAssociations(DeleteInput deleteInput) throws QException
|
||||
{
|
||||
QTableMetaData table = deleteInput.getTable();
|
||||
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||
{
|
||||
// e.g., order -> orderLine
|
||||
QJoinMetaData join = QContext.getQInstance().getJoin(association.getJoinName()); // todo ... ever need to flip?
|
||||
// just assume this, at least for now... if(BooleanUtils.isTrue(association.getDoInserts()))
|
||||
|
||||
QQueryFilter filter = new QQueryFilter();
|
||||
|
||||
if(join.getJoinOns().size() == 1 && join.getJoinOns().get(0).getLeftField().equals(table.getPrimaryKeyField()))
|
||||
{
|
||||
filter.addCriteria(new QFilterCriteria(join.getJoinOns().get(0).getRightField(), QCriteriaOperator.IN, deleteInput.getPrimaryKeys()));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QException("Join of this type is not supported for an associated delete at this time..."));
|
||||
}
|
||||
|
||||
QTableMetaData associatedTable = QContext.getQInstance().getTable(association.getAssociatedTableName());
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTransaction(deleteInput.getTransaction());
|
||||
queryInput.setTableName(association.getAssociatedTableName());
|
||||
queryInput.setFilter(filter);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
List<Serializable> associatedKeys = queryOutput.getRecords().stream().map(r -> r.getValue(associatedTable.getPrimaryKeyField())).toList();
|
||||
|
||||
DeleteInput nextLevelDeleteInput = new DeleteInput();
|
||||
nextLevelDeleteInput.setTransaction(deleteInput.getTransaction());
|
||||
nextLevelDeleteInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelDeleteInput.setPrimaryKeys(associatedKeys);
|
||||
DeleteOutput nextLevelDeleteOutput = new DeleteAction().execute(nextLevelDeleteInput);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static List<QRecord> getRecordListForAuditIfNeeded(DeleteInput deleteInput) throws QException
|
||||
{
|
||||
List<QRecord> recordListForAudit = null;
|
||||
|
||||
AuditLevel auditLevel = DMLAuditAction.getAuditLevel(deleteInput);
|
||||
if(AuditLevel.RECORD.equals(auditLevel) || AuditLevel.FIELD.equals(auditLevel))
|
||||
{
|
||||
List<Serializable> primaryKeyList = deleteInput.getPrimaryKeys();
|
||||
if(CollectionUtils.nullSafeIsEmpty(deleteInput.getPrimaryKeys()) && deleteInput.getQueryFilter() != null)
|
||||
{
|
||||
primaryKeyList = getPrimaryKeysFromQueryFilter(deleteInput);
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(primaryKeyList))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
// always fetch the records - we'll use them anyway for checking not-exist below //
|
||||
////////////////////////////////////////////////////////////////////////////////////
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTransaction(deleteInput.getTransaction());
|
||||
queryInput.setTableName(deleteInput.getTableName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(deleteInput.getTable().getPrimaryKeyField(), QCriteriaOperator.IN, primaryKeyList)));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
recordListForAudit = queryOutput.getRecords();
|
||||
}
|
||||
}
|
||||
|
||||
return (recordListForAudit);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Note - the "can be accessed" part of this method name - it implies that
|
||||
** records that you can't see because of security - that they won't be found
|
||||
** by the query here, so it's the same to you as if they don't exist at all!
|
||||
**
|
||||
** This method, if it finds any missing records, will:
|
||||
** - remove those ids from the deleteInput
|
||||
** - create a QRecord with that id and a not-found error message.
|
||||
*******************************************************************************/
|
||||
private List<QRecord> validateRecordsExistAndCanBeAccessed(DeleteInput deleteInput, List<QRecord> oldRecordList) throws QException
|
||||
{
|
||||
List<QRecord> recordsWithErrors = new ArrayList<>();
|
||||
|
||||
QTableMetaData table = deleteInput.getTable();
|
||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
|
||||
Set<Serializable> primaryKeysToRemoveFromInput = new HashSet<>();
|
||||
|
||||
List<List<Serializable>> pages = CollectionUtils.getPages(deleteInput.getPrimaryKeys(), 1000);
|
||||
for(List<Serializable> page : pages)
|
||||
{
|
||||
List<Serializable> primaryKeysToLookup = new ArrayList<>();
|
||||
for(Serializable primaryKeyValue : page)
|
||||
{
|
||||
if(primaryKeyValue != null)
|
||||
{
|
||||
primaryKeysToLookup.add(primaryKeyValue);
|
||||
}
|
||||
}
|
||||
|
||||
Map<Serializable, QRecord> lookedUpRecords = new HashMap<>();
|
||||
if(CollectionUtils.nullSafeHasContents(oldRecordList))
|
||||
{
|
||||
for(QRecord record : oldRecordList)
|
||||
{
|
||||
Serializable primaryKeyValue = record.getValue(table.getPrimaryKeyField());
|
||||
primaryKeyValue = ValueUtils.getValueAsFieldType(primaryKeyField.getType(), primaryKeyValue);
|
||||
lookedUpRecords.put(primaryKeyValue, record);
|
||||
}
|
||||
}
|
||||
else if(!primaryKeysToLookup.isEmpty())
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTransaction(deleteInput.getTransaction());
|
||||
queryInput.setTableName(table.getName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, primaryKeysToLookup)));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
lookedUpRecords.put(record.getValue(table.getPrimaryKeyField()), record);
|
||||
}
|
||||
}
|
||||
|
||||
for(Serializable primaryKeyValue : page)
|
||||
{
|
||||
primaryKeyValue = ValueUtils.getValueAsFieldType(primaryKeyField.getType(), primaryKeyValue);
|
||||
if(!lookedUpRecords.containsKey(primaryKeyValue))
|
||||
{
|
||||
QRecord recordWithError = new QRecord();
|
||||
recordsWithErrors.add(recordWithError);
|
||||
recordWithError.setValue(primaryKeyField.getName(), primaryKeyValue);
|
||||
recordWithError.addError(NOT_FOUND_ERROR_PREFIX + " for " + primaryKeyField.getLabel() + " = " + primaryKeyValue);
|
||||
primaryKeysToRemoveFromInput.add(primaryKeyValue);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// do one mass removal of any bad keys from the input key list //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
if(!primaryKeysToRemoveFromInput.isEmpty())
|
||||
{
|
||||
deleteInput.getPrimaryKeys().removeAll(primaryKeysToRemoveFromInput);
|
||||
primaryKeysToRemoveFromInput.clear();
|
||||
}
|
||||
}
|
||||
|
||||
return (recordsWithErrors);
|
||||
}
|
||||
|
||||
|
||||
@ -102,7 +295,8 @@ public class DeleteAction
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(deleteInput.getBackend());
|
||||
|
||||
QueryInput queryInput = new QueryInput(deleteInput.getInstance(), deleteInput.getSession());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTransaction(deleteInput.getTransaction());
|
||||
queryInput.setTableName(deleteInput.getTableName());
|
||||
queryInput.setFilter(deleteInput.getQueryFilter());
|
||||
QueryOutput queryOutput = qModule.getQueryInterface().execute(queryInput);
|
||||
|
@ -36,6 +36,8 @@ import com.kingsrook.qqq.backend.core.actions.interfaces.GetInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
|
||||
@ -53,6 +55,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
import com.kingsrook.qqq.backend.core.modules.backend.implementations.utils.BackendQueryFilterUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
@ -64,10 +67,11 @@ import org.apache.commons.lang.NotImplementedException;
|
||||
*******************************************************************************/
|
||||
public class GetAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(InsertAction.class);
|
||||
|
||||
private Optional<AbstractPostQueryCustomizer> postGetRecordCustomizer;
|
||||
|
||||
private GetInput getInput;
|
||||
private QValueFormatter qValueFormatter;
|
||||
private QPossibleValueTranslator qPossibleValueTranslator;
|
||||
|
||||
|
||||
@ -80,6 +84,11 @@ public class GetAction
|
||||
ActionHelper.validateSession(getInput);
|
||||
|
||||
QTableMetaData table = getInput.getTable();
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QException("Requested to Get a record from an unrecognized table: " + getInput.getTableName()));
|
||||
}
|
||||
|
||||
postGetRecordCustomizer = QCodeLoader.getTableCustomizer(AbstractPostQueryCustomizer.class, table, TableCustomizers.POST_QUERY_RECORD.getRole());
|
||||
this.getInput = getInput;
|
||||
|
||||
@ -121,14 +130,34 @@ public class GetAction
|
||||
QRecord recordFromSource = tryToGetFromCacheSource(getInput, getOutput);
|
||||
if(recordFromSource != null)
|
||||
{
|
||||
QRecord recordToCache = mapSourceRecordToCacheRecord(table, recordFromSource);
|
||||
QRecord recordToCache = mapSourceRecordToCacheRecord(table, recordFromSource);
|
||||
boolean shouldCacheRecord = true;
|
||||
|
||||
InsertInput insertInput = new InsertInput(getInput.getInstance());
|
||||
insertInput.setSession(getInput.getSession());
|
||||
insertInput.setTableName(getInput.getTableName());
|
||||
insertInput.setRecords(List.of(recordToCache));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
getOutput.setRecord(insertOutput.getRecords().get(0));
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// see if there are any exclustions that need to be considered for this table //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
recordMatchExclusionLoop:
|
||||
for(CacheUseCase useCase : CollectionUtils.nonNullList(table.getCacheOf().getUseCases()))
|
||||
{
|
||||
for(QQueryFilter filter : CollectionUtils.nonNullList(useCase.getExcludeRecordsMatching()))
|
||||
{
|
||||
if(BackendQueryFilterUtils.doesRecordMatch(filter, recordToCache))
|
||||
{
|
||||
LOG.info("Not caching record because it matches a use case's filter exclusion", new LogPair("record", recordToCache), new LogPair("filter", filter));
|
||||
shouldCacheRecord = false;
|
||||
break recordMatchExclusionLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(shouldCacheRecord)
|
||||
{
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(getInput.getTableName());
|
||||
insertInput.setRecords(List.of(recordToCache));
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
getOutput.setRecord(insertOutput.getRecords().get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -190,8 +219,7 @@ public class GetAction
|
||||
QRecord recordToCache = mapSourceRecordToCacheRecord(table, recordFromSource);
|
||||
recordToCache.setValue(table.getPrimaryKeyField(), cachedRecord.getValue(table.getPrimaryKeyField()));
|
||||
|
||||
UpdateInput updateInput = new UpdateInput(getInput.getInstance());
|
||||
updateInput.setSession(getInput.getSession());
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(getInput.getTableName());
|
||||
updateInput.setRecords(List.of(recordToCache));
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
@ -203,8 +231,7 @@ public class GetAction
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// if the record is no longer in the source, then remove it from the cache //
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
DeleteInput deleteInput = new DeleteInput(getInput.getInstance());
|
||||
deleteInput.setSession(getInput.getSession());
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(getInput.getTableName());
|
||||
deleteInput.setPrimaryKeys(List.of(getOutput.getRecord().getValue(table.getPrimaryKeyField())));
|
||||
new DeleteAction().execute(deleteInput);
|
||||
@ -252,12 +279,13 @@ public class GetAction
|
||||
/////////////////////////////////////////////////////
|
||||
// do a Get on the source table, by the unique key //
|
||||
/////////////////////////////////////////////////////
|
||||
GetInput sourceGetInput = new GetInput(getInput.getInstance());
|
||||
sourceGetInput.setSession(getInput.getSession());
|
||||
GetInput sourceGetInput = new GetInput();
|
||||
sourceGetInput.setTableName(sourceTableName);
|
||||
sourceGetInput.setUniqueKey(getInput.getUniqueKey());
|
||||
GetOutput sourceGetOutput = new GetAction().execute(sourceGetInput);
|
||||
return (sourceGetOutput.getRecord());
|
||||
QRecord outputRecord = sourceGetOutput.getRecord();
|
||||
|
||||
return (outputRecord);
|
||||
}
|
||||
|
||||
|
||||
@ -270,8 +298,7 @@ public class GetAction
|
||||
@Override
|
||||
public GetOutput execute(GetInput getInput) throws QException
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(getInput.getInstance());
|
||||
queryInput.setSession(getInput.getSession());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(getInput.getTableName());
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
@ -302,6 +329,9 @@ public class GetAction
|
||||
}
|
||||
|
||||
queryInput.setFilter(filter);
|
||||
queryInput.setIncludeAssociations(getInput.getIncludeAssociations());
|
||||
queryInput.setAssociationNamesToInclude(getInput.getAssociationNamesToInclude());
|
||||
queryInput.setShouldFetchHeavyFields(getInput.getShouldFetchHeavyFields());
|
||||
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
@ -339,13 +369,13 @@ public class GetAction
|
||||
|
||||
if(getInput.getShouldGenerateDisplayValues())
|
||||
{
|
||||
if(qValueFormatter == null)
|
||||
{
|
||||
qValueFormatter = new QValueFormatter();
|
||||
}
|
||||
qValueFormatter.setDisplayValuesInRecords(getInput.getTable(), List.of(returnRecord));
|
||||
QValueFormatter.setDisplayValuesInRecords(getInput.getTable(), List.of(returnRecord));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// note - shouldFetchHeavyFields should be handled by the underlying action //
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
return (returnRecord);
|
||||
}
|
||||
}
|
||||
|
@ -23,33 +23,43 @@ package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostInsertCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.UniqueKeyHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
|
||||
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.audits.DMLAuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
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 org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -58,7 +68,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOutput>
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(InsertAction.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(InsertAction.class);
|
||||
|
||||
|
||||
|
||||
@ -71,20 +81,42 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
ActionHelper.validateSession(insertInput);
|
||||
QTableMetaData table = insertInput.getTable();
|
||||
|
||||
if(table == null)
|
||||
{
|
||||
throw (new QException("Error: Undefined table: " + insertInput.getTableName()));
|
||||
}
|
||||
|
||||
Optional<AbstractPostInsertCustomizer> postInsertCustomizer = QCodeLoader.getTableCustomizer(AbstractPostInsertCustomizer.class, table, TableCustomizers.POST_INSERT_RECORD.getRole());
|
||||
setAutomationStatusField(insertInput);
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), table, insertInput.getRecords());
|
||||
// todo - need to handle records with errors coming out of here...
|
||||
|
||||
QBackendModuleInterface qModule = getBackendModuleInterface(insertInput);
|
||||
// todo pre-customization - just get to modify the request?
|
||||
|
||||
ValueBehaviorApplier.applyFieldBehaviors(insertInput.getInstance(), table, insertInput.getRecords());
|
||||
setErrorsIfUniqueKeyErrors(insertInput, table);
|
||||
validateRequiredFields(insertInput);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(insertInput.getTable(), insertInput.getRecords(), ValidateRecordSecurityLockHelper.Action.INSERT);
|
||||
|
||||
InsertOutput insertOutput = qModule.getInsertInterface().execute(insertInput);
|
||||
List<String> errors = insertOutput.getRecords().stream().flatMap(r -> r.getErrors().stream()).toList();
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
LOG.warn("Errors in insertAction", logPair("tableName", table.getName()), logPair("errorCount", errors.size()), errors.size() < 10 ? logPair("errors", errors) : logPair("first10Errors", errors.subList(0, 10)));
|
||||
}
|
||||
|
||||
manageAssociations(table, insertOutput.getRecords(), insertInput.getTransaction());
|
||||
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
|
||||
if(insertInput.getOmitDmlAudit())
|
||||
{
|
||||
LOG.debug("Requested to omit DML audit");
|
||||
}
|
||||
else
|
||||
{
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(insertInput).withRecordList(insertOutput.getRecords()));
|
||||
}
|
||||
|
||||
if(postInsertCustomizer.isPresent())
|
||||
{
|
||||
postInsertCustomizer.get().setInsertInput(insertInput);
|
||||
@ -96,6 +128,70 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateRequiredFields(InsertInput insertInput)
|
||||
{
|
||||
QTableMetaData table = insertInput.getTable();
|
||||
Set<QFieldMetaData> requiredFields = table.getFields().values().stream()
|
||||
.filter(f -> f.getIsRequired())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if(!requiredFields.isEmpty())
|
||||
{
|
||||
for(QRecord record : insertInput.getRecords())
|
||||
{
|
||||
for(QFieldMetaData requiredField : requiredFields)
|
||||
{
|
||||
if(record.getValue(requiredField.getName()) == null || (requiredField.getType().isStringLike() && record.getValueString(requiredField.getName()).trim().equals("")))
|
||||
{
|
||||
record.addError("Missing value in required field: " + requiredField.getLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void manageAssociations(QTableMetaData table, List<QRecord> insertedRecords, QBackendTransaction transaction) throws QException
|
||||
{
|
||||
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||
{
|
||||
// e.g., order -> orderLine
|
||||
QJoinMetaData join = QContext.getQInstance().getJoin(association.getJoinName()); // todo ... ever need to flip?
|
||||
// just assume this, at least for now... if(BooleanUtils.isTrue(association.getDoInserts()))
|
||||
|
||||
List<QRecord> nextLevelInserts = new ArrayList<>();
|
||||
for(QRecord record : insertedRecords)
|
||||
{
|
||||
if(record.getAssociatedRecords() != null && record.getAssociatedRecords().containsKey(association.getName()))
|
||||
{
|
||||
for(QRecord associatedRecord : CollectionUtils.nonNullList(record.getAssociatedRecords().get(association.getName())))
|
||||
{
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
associatedRecord.setValue(joinOn.getRightField(), record.getValue(joinOn.getLeftField()));
|
||||
}
|
||||
nextLevelInserts.add(associatedRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InsertInput nextLevelInsertInput = new InsertInput();
|
||||
nextLevelInsertInput.setTransaction(transaction);
|
||||
nextLevelInsertInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelInsertInput.setRecords(nextLevelInserts);
|
||||
InsertOutput nextLevelInsertOutput = new InsertAction().execute(nextLevelInsertInput);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -117,7 +213,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
List<UniqueKey> uniqueKeys = CollectionUtils.nonNullList(table.getUniqueKeys());
|
||||
for(UniqueKey uniqueKey : uniqueKeys)
|
||||
{
|
||||
existingKeys.put(uniqueKey, UniqueKeyHelper.getExistingKeys(insertInput, insertInput.getTransaction(), table, insertInput.getRecords(), uniqueKey));
|
||||
existingKeys.put(uniqueKey, UniqueKeyHelper.getExistingKeys(insertInput.getTransaction(), table, insertInput.getRecords(), uniqueKey).keySet());
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
|
@ -22,8 +22,13 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.tables;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.AbstractPostQueryCustomizer;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
@ -31,14 +36,23 @@ import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.BufferedRecordPipe;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
|
||||
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.query.QCriteriaOperator;
|
||||
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.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.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
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 org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -47,7 +61,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class QueryAction
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QueryAction.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(QueryAction.class);
|
||||
|
||||
private Optional<AbstractPostQueryCustomizer> postQueryRecordCustomizer;
|
||||
|
||||
@ -71,6 +85,14 @@ public class QueryAction
|
||||
queryInput.getRecordPipe().setPostRecordActions(this::postRecordActions);
|
||||
}
|
||||
|
||||
if(queryInput.getIncludeAssociations() && queryInput.getRecordPipe() != null)
|
||||
{
|
||||
//////////////////////////////////////////////
|
||||
// todo - support this in the future maybe? //
|
||||
//////////////////////////////////////////////
|
||||
throw (new QException("Associations may not be fetched into a RecordPipe."));
|
||||
}
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(queryInput.getBackend());
|
||||
// todo pre-customization - just get to modify the request?
|
||||
@ -87,11 +109,119 @@ public class QueryAction
|
||||
postRecordActions(queryOutput.getRecords());
|
||||
}
|
||||
|
||||
if(queryInput.getIncludeAssociations())
|
||||
{
|
||||
manageAssociations(queryInput, queryOutput);
|
||||
}
|
||||
|
||||
return queryOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void manageAssociations(QueryInput queryInput, QueryOutput queryOutput) throws QException
|
||||
{
|
||||
QTableMetaData table = queryInput.getTable();
|
||||
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||
{
|
||||
if(queryInput.getAssociationNamesToInclude() == null || queryInput.getAssociationNamesToInclude().contains(association.getName()))
|
||||
{
|
||||
// e.g., order -> orderLine
|
||||
QJoinMetaData join = QContext.getQInstance().getJoin(association.getJoinName()); // todo ... ever need to flip?
|
||||
// just assume this, at least for now... if(BooleanUtils.isTrue(association.getDoInserts()))
|
||||
|
||||
QueryInput nextLevelQueryInput = new QueryInput();
|
||||
nextLevelQueryInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelQueryInput.setIncludeAssociations(true);
|
||||
nextLevelQueryInput.setAssociationNamesToInclude(buildNextLevelAssociationNamesToInclude(association.getName(), queryInput.getAssociationNamesToInclude()));
|
||||
|
||||
QQueryFilter filter = new QQueryFilter();
|
||||
nextLevelQueryInput.setFilter(filter);
|
||||
|
||||
ListingHash<List<Serializable>, QRecord> outerResultMap = new ListingHash<>();
|
||||
|
||||
if(join.getJoinOns().size() == 1)
|
||||
{
|
||||
JoinOn joinOn = join.getJoinOns().get(0);
|
||||
Set<Serializable> values = new HashSet<>();
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
Serializable value = record.getValue(joinOn.getLeftField());
|
||||
values.add(value);
|
||||
outerResultMap.add(List.of(value), record);
|
||||
}
|
||||
filter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.IN, new ArrayList<>(values)));
|
||||
}
|
||||
else
|
||||
{
|
||||
filter.setBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
||||
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
QQueryFilter subFilter = new QQueryFilter();
|
||||
filter.addSubFilter(subFilter);
|
||||
List<Serializable> values = new ArrayList<>();
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
Serializable value = record.getValue(joinOn.getLeftField());
|
||||
values.add(value);
|
||||
subFilter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.EQUALS, value));
|
||||
}
|
||||
outerResultMap.add(values, record);
|
||||
}
|
||||
}
|
||||
|
||||
QueryOutput nextLevelQueryOutput = new QueryAction().execute(nextLevelQueryInput);
|
||||
for(QRecord record : nextLevelQueryOutput.getRecords())
|
||||
{
|
||||
List<Serializable> values = new ArrayList<>();
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
Serializable value = record.getValue(joinOn.getRightField());
|
||||
values.add(value);
|
||||
}
|
||||
|
||||
if(outerResultMap.containsKey(values))
|
||||
{
|
||||
for(QRecord outerRecord : outerResultMap.get(values))
|
||||
{
|
||||
outerRecord.withAssociatedRecord(association.getName(), record);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Collection<String> buildNextLevelAssociationNamesToInclude(String name, Collection<String> associationNamesToInclude)
|
||||
{
|
||||
if(associationNamesToInclude == null)
|
||||
{
|
||||
return (associationNamesToInclude);
|
||||
}
|
||||
|
||||
Set<String> rs = new HashSet<>();
|
||||
for(String nextLevelCandidateName : associationNamesToInclude)
|
||||
{
|
||||
if(nextLevelCandidateName.startsWith(name + "."))
|
||||
{
|
||||
rs.add(nextLevelCandidateName.replaceFirst(name + ".", ""));
|
||||
}
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Run the necessary actions on a list of records (which must be a mutable list - e.g.,
|
||||
** not one created via List.of()). This may include setting display values,
|
||||
|
@ -22,15 +22,46 @@
|
||||
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.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.audits.DMLAuditAction;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.AutomationStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationStatusUpdater;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.helpers.ValidateRecordSecurityLockHelper;
|
||||
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.audits.DMLAuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.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;
|
||||
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.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
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.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -39,6 +70,12 @@ import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
|
||||
*******************************************************************************/
|
||||
public class UpdateAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(UpdateAction.class);
|
||||
|
||||
public static final String NOT_FOUND_ERROR_PREFIX = "No record was found to update";
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -50,12 +87,310 @@ public class UpdateAction
|
||||
ValueBehaviorApplier.applyFieldBehaviors(updateInput.getInstance(), updateInput.getTable(), updateInput.getRecords());
|
||||
// todo - need to handle records with errors coming out of here...
|
||||
|
||||
List<QRecord> oldRecordList = getOldRecordListForAuditIfNeeded(updateInput);
|
||||
|
||||
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
|
||||
QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(updateInput.getBackend());
|
||||
|
||||
validatePrimaryKeysAreGiven(updateInput);
|
||||
validateRecordsExistAndCanBeAccessed(updateInput, oldRecordList);
|
||||
validateRequiredFields(updateInput);
|
||||
ValidateRecordSecurityLockHelper.validateSecurityFields(updateInput.getTable(), updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
|
||||
|
||||
// todo pre-customization - just get to modify the request?
|
||||
UpdateOutput updateResult = qModule.getUpdateInterface().execute(updateInput);
|
||||
UpdateOutput updateOutput = qModule.getUpdateInterface().execute(updateInput);
|
||||
// todo post-customization - can do whatever w/ the result if you want
|
||||
return updateResult;
|
||||
|
||||
List<String> errors = updateOutput.getRecords().stream().flatMap(r -> r.getErrors().stream()).toList();
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
LOG.warn("Errors in updateAction", logPair("tableName", updateInput.getTableName()), logPair("errorCount", errors.size()), errors.size() < 10 ? logPair("errors", errors) : logPair("first10Errors", errors.subList(0, 10)));
|
||||
}
|
||||
|
||||
manageAssociations(updateInput);
|
||||
|
||||
if(updateInput.getOmitDmlAudit())
|
||||
{
|
||||
LOG.debug("Requested to omit DML audit");
|
||||
}
|
||||
else
|
||||
{
|
||||
new DMLAuditAction().execute(new DMLAuditInput().withTableActionInput(updateInput).withRecordList(updateOutput.getRecords()).withOldRecordList(oldRecordList));
|
||||
}
|
||||
|
||||
return updateOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validatePrimaryKeysAreGiven(UpdateInput updateInput)
|
||||
{
|
||||
QTableMetaData table = updateInput.getTable();
|
||||
for(QRecord record : CollectionUtils.nonNullList(updateInput.getRecords()))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// to update a record, we must have its primary key value - so - check - if it's missing, mark it as an error //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(record.getValue(table.getPrimaryKeyField()) == null)
|
||||
{
|
||||
record.addError("Missing value in primary key field");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Note - the "can be accessed" part of this method name - it implies that
|
||||
** records that you can't see because of security - that they won't be found
|
||||
** by the query here, so it's the same to you as if they don't exist at all!
|
||||
*******************************************************************************/
|
||||
private void validateRecordsExistAndCanBeAccessed(UpdateInput updateInput, List<QRecord> oldRecordList) throws QException
|
||||
{
|
||||
QTableMetaData table = updateInput.getTable();
|
||||
QFieldMetaData primaryKeyField = table.getField(table.getPrimaryKeyField());
|
||||
|
||||
for(List<QRecord> page : CollectionUtils.getPages(updateInput.getRecords(), 1000))
|
||||
{
|
||||
List<Serializable> primaryKeysToLookup = new ArrayList<>();
|
||||
for(QRecord record : page)
|
||||
{
|
||||
Serializable primaryKeyValue = record.getValue(table.getPrimaryKeyField());
|
||||
if(primaryKeyValue != null)
|
||||
{
|
||||
primaryKeysToLookup.add(primaryKeyValue);
|
||||
}
|
||||
}
|
||||
|
||||
Map<Serializable, QRecord> lookedUpRecords = new HashMap<>();
|
||||
if(CollectionUtils.nullSafeHasContents(oldRecordList))
|
||||
{
|
||||
for(QRecord record : oldRecordList)
|
||||
{
|
||||
lookedUpRecords.put(record.getValue(table.getPrimaryKeyField()), record);
|
||||
}
|
||||
}
|
||||
else if(!primaryKeysToLookup.isEmpty())
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTransaction(updateInput.getTransaction());
|
||||
queryInput.setTableName(table.getName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, primaryKeysToLookup)));
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
lookedUpRecords.put(record.getValue(table.getPrimaryKeyField()), record);
|
||||
}
|
||||
}
|
||||
|
||||
for(QRecord record : page)
|
||||
{
|
||||
Serializable value = ValueUtils.getValueAsFieldType(primaryKeyField.getType(), record.getValue(table.getPrimaryKeyField()));
|
||||
if(value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!lookedUpRecords.containsKey(value))
|
||||
{
|
||||
record.addError(NOT_FOUND_ERROR_PREFIX + " for " + primaryKeyField.getLabel() + " = " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateRequiredFields(UpdateInput updateInput)
|
||||
{
|
||||
QTableMetaData table = updateInput.getTable();
|
||||
Set<QFieldMetaData> requiredFields = table.getFields().values().stream()
|
||||
.filter(f -> f.getIsRequired())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if(!requiredFields.isEmpty())
|
||||
{
|
||||
for(QRecord record : CollectionUtils.nonNullList(updateInput.getRecords()))
|
||||
{
|
||||
for(QFieldMetaData requiredField : requiredFields)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// only consider fields that were set in the record to be updated (e.g., "patch" semantic) //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(record.getValues().containsKey(requiredField.getName()))
|
||||
{
|
||||
if(record.getValue(requiredField.getName()) == null || (requiredField.getType().isStringLike() && record.getValueString(requiredField.getName()).trim().equals("")))
|
||||
{
|
||||
record.addError("Missing value in required field: " + requiredField.getLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void manageAssociations(UpdateInput updateInput) throws QException
|
||||
{
|
||||
QTableMetaData table = updateInput.getTable();
|
||||
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||
{
|
||||
// e.g., order -> orderLine
|
||||
QTableMetaData associatedTable = QContext.getQInstance().getTable(association.getAssociatedTableName());
|
||||
QJoinMetaData join = QContext.getQInstance().getJoin(association.getJoinName()); // todo ... ever need to flip?
|
||||
// just assume this, at least for now... if(BooleanUtils.isTrue(association.getDoInserts()))
|
||||
|
||||
for(List<QRecord> page : CollectionUtils.getPages(updateInput.getRecords(), 500))
|
||||
{
|
||||
List<QRecord> nextLevelUpdates = new ArrayList<>();
|
||||
List<QRecord> nextLevelInserts = new ArrayList<>();
|
||||
QQueryFilter findDeletesFilter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
||||
boolean lookForDeletes = false;
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// for each updated record, look at as associations //
|
||||
//////////////////////////////////////////////////////
|
||||
for(QRecord record : page)
|
||||
{
|
||||
if(record.getAssociatedRecords() != null && record.getAssociatedRecords().containsKey(association.getName()))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// build a sub-query to find the children of this record - and we'll exclude (below) any whose ids are given //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QQueryFilter subFilter = new QQueryFilter();
|
||||
findDeletesFilter.addSubFilter(subFilter);
|
||||
lookForDeletes = true;
|
||||
List<Serializable> idsBeingUpdated = new ArrayList<>();
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
subFilter.addCriteria(new QFilterCriteria(joinOn.getRightField(), QCriteriaOperator.EQUALS, record.getValue(joinOn.getLeftField())));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// for any associated records present here, figure out if they're being inserted (no primaryKey) or updated //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QRecord associatedRecord : CollectionUtils.nonNullList(record.getAssociatedRecords().get(association.getName())))
|
||||
{
|
||||
Serializable associatedId = associatedRecord.getValue(associatedTable.getPrimaryKeyField());
|
||||
if(associatedId == null)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if inserting, add to the inserts list, and propagate values from the header record down to the child //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(JoinOn joinOn : join.getJoinOns())
|
||||
{
|
||||
associatedRecord.setValue(joinOn.getRightField(), record.getValue(joinOn.getLeftField()));
|
||||
}
|
||||
nextLevelInserts.add(associatedRecord);
|
||||
}
|
||||
else
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// if updating, add to the updates list, and add the id as one to not delete //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
idsBeingUpdated.add(associatedId);
|
||||
nextLevelUpdates.add(associatedRecord);
|
||||
}
|
||||
}
|
||||
|
||||
if(!idsBeingUpdated.isEmpty())
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// if any records are being updated, add them to the query to NOT be deleted //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
subFilter.addCriteria(new QFilterCriteria(associatedTable.getPrimaryKeyField(), QCriteriaOperator.NOT_IN, idsBeingUpdated));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(lookForDeletes)
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTransaction(updateInput.getTransaction());
|
||||
queryInput.setTableName(associatedTable.getName());
|
||||
queryInput.setFilter(findDeletesFilter);
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
if(!queryOutput.getRecords().isEmpty())
|
||||
{
|
||||
LOG.debug("Deleting associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", queryOutput.getRecords().size()));
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTransaction(updateInput.getTransaction());
|
||||
deleteInput.setTableName(association.getAssociatedTableName());
|
||||
deleteInput.setPrimaryKeys(queryOutput.getRecords().stream().map(r -> r.getValue(associatedTable.getPrimaryKeyField())).collect(Collectors.toList()));
|
||||
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
||||
}
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(nextLevelUpdates))
|
||||
{
|
||||
LOG.debug("Updating associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", nextLevelUpdates.size()));
|
||||
UpdateInput nextLevelUpdateInput = new UpdateInput();
|
||||
nextLevelUpdateInput.setTransaction(updateInput.getTransaction());
|
||||
nextLevelUpdateInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelUpdateInput.setRecords(nextLevelUpdates);
|
||||
UpdateOutput nextLevelUpdateOutput = new UpdateAction().execute(nextLevelUpdateInput);
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(nextLevelInserts))
|
||||
{
|
||||
LOG.debug("Inserting associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", nextLevelUpdates.size()));
|
||||
InsertInput nextLevelInsertInput = new InsertInput();
|
||||
nextLevelInsertInput.setTransaction(updateInput.getTransaction());
|
||||
nextLevelInsertInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelInsertInput.setRecords(nextLevelInserts);
|
||||
InsertOutput nextLevelInsertOutput = new InsertAction().execute(nextLevelInsertInput);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static List<QRecord> getOldRecordListForAuditIfNeeded(UpdateInput updateInput)
|
||||
{
|
||||
if(updateInput.getOmitDmlAudit())
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
AuditLevel auditLevel = DMLAuditAction.getAuditLevel(updateInput);
|
||||
List<QRecord> oldRecordList = null;
|
||||
if(AuditLevel.FIELD.equals(auditLevel))
|
||||
{
|
||||
String primaryKeyField = updateInput.getTable().getPrimaryKeyField();
|
||||
List<Serializable> pkeysBeingUpdated = CollectionUtils.nonNullList(updateInput.getRecords()).stream().map(r -> r.getValue(primaryKeyField)).toList();
|
||||
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTransaction(updateInput.getTransaction());
|
||||
queryInput.setTableName(updateInput.getTableName());
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(primaryKeyField, QCriteriaOperator.IN, pkeysBeingUpdated)));
|
||||
// todo - need a limit? what if too many??
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
|
||||
oldRecordList = queryOutput.getRecords();
|
||||
}
|
||||
return oldRecordList;
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Error getting old record list for audit", e, logPair("table", updateInput.getTableName()));
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -24,15 +24,14 @@ package com.kingsrook.qqq.backend.core.actions.tables.helpers;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
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;
|
||||
@ -55,14 +54,13 @@ public class UniqueKeyHelper
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Set<List<Serializable>> getExistingKeys(AbstractActionInput actionInput, QBackendTransaction transaction, QTableMetaData table, List<QRecord> recordList, UniqueKey uniqueKey) throws QException
|
||||
public static Map<List<Serializable>, Serializable> getExistingKeys(QBackendTransaction transaction, QTableMetaData table, List<QRecord> recordList, UniqueKey uniqueKey) throws QException
|
||||
{
|
||||
List<String> ukFieldNames = uniqueKey.getFieldNames();
|
||||
Set<List<Serializable>> existingRecords = new HashSet<>();
|
||||
List<String> ukFieldNames = uniqueKey.getFieldNames();
|
||||
Map<List<Serializable>, Serializable> existingRecords = new HashMap<>();
|
||||
if(ukFieldNames != null)
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(actionInput.getInstance());
|
||||
queryInput.setSession(actionInput.getSession());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(table.getName());
|
||||
queryInput.setTransaction(transaction);
|
||||
|
||||
@ -115,8 +113,10 @@ public class UniqueKeyHelper
|
||||
for(QRecord record : queryOutput.getRecords())
|
||||
{
|
||||
Optional<List<Serializable>> keyValues = getKeyValues(table, uniqueKey, record);
|
||||
keyValues.ifPresent(existingRecords::add);
|
||||
|
||||
if(keyValues.isPresent())
|
||||
{
|
||||
existingRecords.put(keyValues.get(), record.getValue(table.getPrimaryKeyField()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,313 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.tables.helpers;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
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.query.QCriteriaOperator;
|
||||
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.actions.tables.query.QueryInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
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.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class ValidateRecordSecurityLockHelper
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(ValidateRecordSecurityLockHelper.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public enum Action
|
||||
{
|
||||
INSERT,
|
||||
UPDATE,
|
||||
SELECT
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void validateSecurityFields(QTableMetaData table, List<QRecord> records, Action action) throws QException
|
||||
{
|
||||
List<RecordSecurityLock> locksToCheck = getRecordSecurityLocks(table);
|
||||
if(CollectionUtils.nullSafeIsEmpty(locksToCheck))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// actually check lock values //
|
||||
////////////////////////////////
|
||||
for(RecordSecurityLock recordSecurityLock : locksToCheck)
|
||||
{
|
||||
if(CollectionUtils.nullSafeIsEmpty(recordSecurityLock.getJoinNameChain()))
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// handle the value being in the table we're inserting/updating (e.g., no join) //
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
QFieldMetaData field = table.getField(recordSecurityLock.getFieldName());
|
||||
|
||||
for(QRecord record : records)
|
||||
{
|
||||
if(action.equals(Action.UPDATE) && !record.getValues().containsKey(field.getName()))
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
// if not updating the security field, then no error can come from it! //
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
continue;
|
||||
}
|
||||
|
||||
Serializable recordSecurityValue = record.getValue(field.getName());
|
||||
validateRecordSecurityValue(table, record, recordSecurityLock, recordSecurityValue, field.getType(), action);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// else look for the joined record - if it isn't found, assume a fail - else validate security value if found //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QJoinMetaData leftMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(0));
|
||||
QJoinMetaData rightMostJoin = QContext.getQInstance().getJoin(recordSecurityLock.getJoinNameChain().get(recordSecurityLock.getJoinNameChain().size() - 1));
|
||||
QTableMetaData leftMostJoinTable = QContext.getQInstance().getTable(leftMostJoin.getLeftTable());
|
||||
|
||||
for(List<QRecord> inputRecordPage : CollectionUtils.getPages(records, 500))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// set up a query for joined records //
|
||||
// query will be like (fkey1=? and fkey2=?) OR (fkey1=? and fkey2=?) OR (fkey1=? and fkey2=?) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(leftMostJoin.getLeftTable());
|
||||
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
|
||||
queryInput.setFilter(filter);
|
||||
|
||||
for(String joinName : recordSecurityLock.getJoinNameChain())
|
||||
{
|
||||
///////////////////////////////////////
|
||||
// we don't need the right-most join //
|
||||
///////////////////////////////////////
|
||||
if(!joinName.equals(rightMostJoin.getName()))
|
||||
{
|
||||
queryInput.withQueryJoin(new QueryJoin().withJoinMetaData(QContext.getQInstance().getJoin(joinName)).withSelect(true));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// foreach input record (in this page), put it in a listing hash, with key = list of join-values //
|
||||
// e.g., (17,47)=(QRecord1), (18,48)=(QRecord2,QRecord3) //
|
||||
// also build up the query's sub-filters here (only adding them if they're unique). //
|
||||
// e.g., 2 order-lines referencing the same orderId don't need to be added to the query twice //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
ListingHash<List<Serializable>, QRecord> inputRecordMapByJoinFields = new ListingHash<>();
|
||||
for(QRecord inputRecord : inputRecordPage)
|
||||
{
|
||||
List<Serializable> inputRecordJoinValues = new ArrayList<>();
|
||||
QQueryFilter subFilter = new QQueryFilter();
|
||||
|
||||
for(JoinOn joinOn : rightMostJoin.getJoinOns())
|
||||
{
|
||||
Serializable inputRecordValue = inputRecord.getValue(joinOn.getRightField());
|
||||
inputRecordJoinValues.add(inputRecordValue);
|
||||
|
||||
subFilter.addCriteria(inputRecordValue == null
|
||||
? new QFilterCriteria(rightMostJoin.getLeftTable() + "." + joinOn.getLeftField(), QCriteriaOperator.IS_BLANK)
|
||||
: new QFilterCriteria(rightMostJoin.getLeftTable() + "." + joinOn.getLeftField(), QCriteriaOperator.EQUALS, inputRecordValue));
|
||||
}
|
||||
|
||||
if(!inputRecordMapByJoinFields.containsKey(inputRecordJoinValues))
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// only add this sub-filter if it's for a list of keys we haven't seen before //
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
filter.addSubFilter(subFilter);
|
||||
}
|
||||
|
||||
inputRecordMapByJoinFields.add(inputRecordJoinValues, inputRecord);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// execute the query for joined records - then put them in a map with keys corresponding to the join values //
|
||||
// e.g., (17,47)=(JoinRecord), (18,48)=(JoinRecord) //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QueryOutput queryOutput = new QueryAction().execute(queryInput);
|
||||
Map<List<Serializable>, QRecord> joinRecordMapByJoinFields = new HashMap<>();
|
||||
for(QRecord joinRecord : queryOutput.getRecords())
|
||||
{
|
||||
List<Serializable> joinRecordValues = new ArrayList<>();
|
||||
for(JoinOn joinOn : rightMostJoin.getJoinOns())
|
||||
{
|
||||
Serializable joinValue = joinRecord.getValue(rightMostJoin.getLeftTable() + "." + joinOn.getLeftField());
|
||||
if(joinValue == null && joinRecord.getValues().keySet().stream().anyMatch(n -> !n.contains(".")))
|
||||
{
|
||||
joinValue = joinRecord.getValue(joinOn.getLeftField());
|
||||
}
|
||||
joinRecordValues.add(joinValue);
|
||||
}
|
||||
|
||||
joinRecordMapByJoinFields.put(joinRecordValues, joinRecord);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// now for each input record, look for its joinRecord - if it isn't found, then this insert //
|
||||
// isn't allowed. if it is found, then validate its value matches this session's security keys //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(Map.Entry<List<Serializable>, List<QRecord>> entry : inputRecordMapByJoinFields.entrySet())
|
||||
{
|
||||
List<Serializable> inputRecordJoinValues = entry.getKey();
|
||||
List<QRecord> inputRecords = entry.getValue();
|
||||
if(joinRecordMapByJoinFields.containsKey(inputRecordJoinValues))
|
||||
{
|
||||
QRecord joinRecord = joinRecordMapByJoinFields.get(inputRecordJoinValues);
|
||||
|
||||
String fieldName = recordSecurityLock.getFieldName().replaceFirst(".*\\.", "");
|
||||
QFieldMetaData field = leftMostJoinTable.getField(fieldName);
|
||||
Serializable recordSecurityValue = joinRecord.getValue(fieldName);
|
||||
if(recordSecurityValue == null && joinRecord.getValues().keySet().stream().anyMatch(n -> n.contains(".")))
|
||||
{
|
||||
recordSecurityValue = joinRecord.getValue(recordSecurityLock.getFieldName());
|
||||
}
|
||||
|
||||
for(QRecord inputRecord : inputRecords)
|
||||
{
|
||||
validateRecordSecurityValue(table, inputRecord, recordSecurityLock, recordSecurityValue, field.getType(), action);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for(QRecord inputRecord : inputRecords)
|
||||
{
|
||||
if(RecordSecurityLock.NullValueBehavior.DENY.equals(recordSecurityLock.getNullValueBehavior()))
|
||||
{
|
||||
inputRecord.addError("You do not have permission to " + action.name().toLowerCase() + " this record - the referenced " + leftMostJoinTable.getLabel() + " was not found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static List<RecordSecurityLock> getRecordSecurityLocks(QTableMetaData table)
|
||||
{
|
||||
List<RecordSecurityLock> recordSecurityLocks = table.getRecordSecurityLocks();
|
||||
List<RecordSecurityLock> locksToCheck = new ArrayList<>();
|
||||
|
||||
////////////////////////////////////////
|
||||
// if there are no locks, just return //
|
||||
////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeIsEmpty(recordSecurityLocks))
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// decide if any locks need checked - where one may not need checked if it has an all-access key, and the user has all-access //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(RecordSecurityLock recordSecurityLock : recordSecurityLocks)
|
||||
{
|
||||
QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
|
||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && QContext.getQSession().hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
|
||||
{
|
||||
LOG.debug("Session has " + securityKeyType.getAllAccessKeyName() + " - not checking this lock.");
|
||||
}
|
||||
else
|
||||
{
|
||||
locksToCheck.add(recordSecurityLock);
|
||||
}
|
||||
}
|
||||
|
||||
return (locksToCheck);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
static void validateRecordSecurityValue(QTableMetaData table, QRecord record, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action)
|
||||
{
|
||||
if(recordSecurityValue == null)
|
||||
{
|
||||
/////////////////////////////////////////////////////////////////
|
||||
// handle null values - error if the NullValueBehavior is DENY //
|
||||
/////////////////////////////////////////////////////////////////
|
||||
if(RecordSecurityLock.NullValueBehavior.DENY.equals(recordSecurityLock.getNullValueBehavior()))
|
||||
{
|
||||
String lockLabel = CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()) ? recordSecurityLock.getSecurityKeyType() : table.getField(recordSecurityLock.getFieldName()).getLabel();
|
||||
record.addError("You do not have permission to " + action.name().toLowerCase() + " a record without a value in the field: " + lockLabel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!QContext.getQSession().hasSecurityKeyValue(recordSecurityLock.getSecurityKeyType(), recordSecurityValue, fieldType))
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// avoid telling the user a value from a foreign record that they didn't pass in themselves. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
record.addError("You do not have permission to " + action.name().toLowerCase() + " this record.");
|
||||
}
|
||||
else
|
||||
{
|
||||
QFieldMetaData field = table.getField(recordSecurityLock.getFieldName());
|
||||
record.addError("You do not have permission to " + action.name().toLowerCase() + " a record with a value of " + recordSecurityValue + " in the field: " + field.getLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -82,8 +82,7 @@ public class RenderTemplateAction extends AbstractQActionFunction<RenderTemplate
|
||||
*******************************************************************************/
|
||||
public static String render(AbstractActionInput parentActionInput, TemplateType templateType, Map<String, Object> context, String code) throws QException
|
||||
{
|
||||
RenderTemplateInput renderTemplateInput = new RenderTemplateInput(parentActionInput.getInstance());
|
||||
renderTemplateInput.setSession(parentActionInput.getSession());
|
||||
RenderTemplateInput renderTemplateInput = new RenderTemplateInput();
|
||||
renderTemplateInput.setCode(code);
|
||||
renderTemplateInput.setContext(context);
|
||||
renderTemplateInput.setTemplateType(templateType);
|
||||
|
@ -23,21 +23,81 @@ package com.kingsrook.qqq.backend.core.actions.values;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface to be implemented by user-defined code that serves as the backing
|
||||
** for a CUSTOM type possibleValueSource
|
||||
**
|
||||
** Type parameter `T` is the id-type of the possible value.
|
||||
*******************************************************************************/
|
||||
public interface QCustomPossibleValueProvider
|
||||
public interface QCustomPossibleValueProvider<T extends Serializable>
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
QPossibleValue<T> getPossibleValue(Serializable idValue);
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
QPossibleValue<?> getPossibleValue(Serializable idValue);
|
||||
List<QPossibleValue<T>> search(SearchPossibleValueSourceInput input) throws QException;
|
||||
|
||||
// todo - get/search list of possible values
|
||||
|
||||
/*******************************************************************************
|
||||
** The input list of ids might come through as a type that isn't the same as
|
||||
** the type of the ids in the enum (e.g., strings from a frontend, integers
|
||||
** in an enum). So, this method looks maps a list of input ids to the requested type.
|
||||
*******************************************************************************/
|
||||
default List<T> convertInputIdsToIdType(Class<T> type, List<Serializable> inputIdList)
|
||||
{
|
||||
List<T> rs = new ArrayList<>();
|
||||
if(CollectionUtils.nullSafeIsEmpty(inputIdList))
|
||||
{
|
||||
return (rs);
|
||||
}
|
||||
|
||||
for(Serializable serializable : inputIdList)
|
||||
{
|
||||
rs.add(ValueUtils.getValueAsType(type, serializable));
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
default boolean doesPossibleValueMatchSearchInput(List<T> idsInType, QPossibleValue<T> possibleValue, SearchPossibleValueSourceInput input)
|
||||
{
|
||||
boolean match = false;
|
||||
if(input.getIdList() != null)
|
||||
{
|
||||
if(idsInType.contains(possibleValue.getId()))
|
||||
{
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(StringUtils.hasContent(input.getSearchTerm()))
|
||||
{
|
||||
match = possibleValue.getLabel().toLowerCase().startsWith(input.getSearchTerm().toLowerCase());
|
||||
}
|
||||
else
|
||||
{
|
||||
match = true;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,9 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
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.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@ -55,8 +57,7 @@ import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -65,16 +66,25 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class QPossibleValueTranslator
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QPossibleValueTranslator.class);
|
||||
|
||||
private final QInstance qInstance;
|
||||
private final QSession session;
|
||||
private static final QLogger LOG = QLogger.getLogger(QPossibleValueTranslator.class);
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// top-level keys are pvsNames (not table names) //
|
||||
// 2nd-level keys are pkey values from the PVS table //
|
||||
///////////////////////////////////////////////////////
|
||||
private Map<String, Map<Serializable, String>> possibleValueCache;
|
||||
private Map<String, Map<Serializable, String>> possibleValueCache = new HashMap<>();
|
||||
|
||||
// todo not commit - remove instance & session - use Context
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueTranslator()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -83,10 +93,6 @@ public class QPossibleValueTranslator
|
||||
*******************************************************************************/
|
||||
public QPossibleValueTranslator(QInstance qInstance, QSession session)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
this.session = session;
|
||||
|
||||
this.possibleValueCache = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
@ -139,7 +145,7 @@ public class QPossibleValueTranslator
|
||||
{
|
||||
try
|
||||
{
|
||||
QTableMetaData joinTable = qInstance.getTable(queryJoin.getJoinTable());
|
||||
QTableMetaData joinTable = QContext.getQInstance().getTable(queryJoin.getJoinTable());
|
||||
for(QFieldMetaData field : joinTable.getFields().values())
|
||||
{
|
||||
String joinFieldName = Objects.requireNonNullElse(queryJoin.getAlias(), joinTable.getName()) + "." + field.getName();
|
||||
@ -150,7 +156,7 @@ public class QPossibleValueTranslator
|
||||
///////////////////////////////////////////////
|
||||
// avoid circling-back upon the source table //
|
||||
///////////////////////////////////////////////
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
QPossibleValueSource possibleValueSource = QContext.getQInstance().getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
if(QPossibleValueSourceType.TABLE.equals(possibleValueSource.getType()) && table.getName().equals(possibleValueSource.getTableName()))
|
||||
{
|
||||
continue;
|
||||
@ -210,7 +216,7 @@ public class QPossibleValueTranslator
|
||||
*******************************************************************************/
|
||||
public String translatePossibleValue(QFieldMetaData field, Serializable value)
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
QPossibleValueSource possibleValueSource = QContext.getQInstance().getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
if(possibleValueSource == null)
|
||||
{
|
||||
LOG.error("Missing possible value source named [" + field.getPossibleValueSourceName() + "] when formatting value for field [" + field.getName() + "]");
|
||||
@ -412,7 +418,16 @@ public class QPossibleValueTranslator
|
||||
if(queryJoin.getSelect())
|
||||
{
|
||||
String aliasOrTableName = Objects.requireNonNullElse(queryJoin.getAlias(), queryJoin.getJoinTable());
|
||||
primePvsCacheTableListingHashLoader(qInstance.getTable(queryJoin.getJoinTable()), fieldsByPvsTable, pvsesByTable, aliasOrTableName + ".", queryJoin.getJoinTable(), limitedToFieldNames);
|
||||
primePvsCacheTableListingHashLoader(QContext.getQInstance().getTable(queryJoin.getJoinTable()), fieldsByPvsTable, pvsesByTable, aliasOrTableName + ".", queryJoin.getJoinTable(), limitedToFieldNames);
|
||||
}
|
||||
}
|
||||
|
||||
for(Map.Entry<String, Map<Serializable, String>> entry : possibleValueCache.entrySet())
|
||||
{
|
||||
int size = entry.getValue().size();
|
||||
if(size > 50_000)
|
||||
{
|
||||
LOG.debug("Found a big PVS cache - clearing it.", logPair("name", entry.getKey()), logPair("size", size));
|
||||
}
|
||||
}
|
||||
|
||||
@ -463,7 +478,7 @@ public class QPossibleValueTranslator
|
||||
{
|
||||
for(QFieldMetaData field : table.getFields().values())
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = qInstance.getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
QPossibleValueSource possibleValueSource = QContext.getQInstance().getPossibleValueSource(field.getPossibleValueSourceName());
|
||||
if(possibleValueSource != null && possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
|
||||
{
|
||||
if(limitedToFieldNames != null && !limitedToFieldNames.contains(fieldNamePrefix + field.getName()))
|
||||
@ -499,12 +514,11 @@ public class QPossibleValueTranslator
|
||||
|
||||
try
|
||||
{
|
||||
String primaryKeyField = qInstance.getTable(tableName).getPrimaryKeyField();
|
||||
String primaryKeyField = QContext.getQInstance().getTable(tableName).getPrimaryKeyField();
|
||||
|
||||
for(List<Serializable> page : CollectionUtils.getPages(values, 1000))
|
||||
{
|
||||
QueryInput queryInput = new QueryInput(qInstance);
|
||||
queryInput.setSession(session);
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(tableName);
|
||||
queryInput.setFilter(new QQueryFilter().withCriteria(new QFilterCriteria(primaryKeyField, QCriteriaOperator.IN, page)));
|
||||
|
||||
@ -521,7 +535,7 @@ public class QPossibleValueTranslator
|
||||
{
|
||||
if(possibleValueSource.getType().equals(QPossibleValueSourceType.TABLE))
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(possibleValueSource.getTableName());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(possibleValueSource.getTableName());
|
||||
for(String recordLabelField : CollectionUtils.nonNullList(table.getRecordLabelFields()))
|
||||
{
|
||||
QFieldMetaData field = table.getField(recordLabelField);
|
||||
|
@ -25,18 +25,18 @@ package com.kingsrook.qqq.backend.core.actions.values;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
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.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -45,10 +45,12 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class QValueFormatter
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QValueFormatter.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(QValueFormatter.class);
|
||||
|
||||
private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd h:mm a");
|
||||
private static DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss a");
|
||||
private static DateTimeFormatter dateTimeWithZoneFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss a z");
|
||||
private static DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static DateTimeFormatter localTimeFormatter = DateTimeFormatter.ofPattern("h:mm:ss a");
|
||||
|
||||
|
||||
|
||||
@ -57,23 +59,6 @@ public class QValueFormatter
|
||||
*******************************************************************************/
|
||||
public static String formatValue(QFieldMetaData field, Serializable value)
|
||||
{
|
||||
if(QFieldType.BOOLEAN.equals(field.getType()))
|
||||
{
|
||||
Boolean b = ValueUtils.getValueAsBoolean(value);
|
||||
if(b == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
else if(b)
|
||||
{
|
||||
return ("Yes");
|
||||
}
|
||||
else
|
||||
{
|
||||
return ("No");
|
||||
}
|
||||
}
|
||||
|
||||
return (formatValue(field.getDisplayFormat(), field.getName(), value));
|
||||
}
|
||||
|
||||
@ -103,6 +88,22 @@ public class QValueFormatter
|
||||
return (null);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// try to apply some type-specific defaults, if we were requested to just format as a string. //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if("%s".equals(displayFormat))
|
||||
{
|
||||
if(value instanceof Boolean b)
|
||||
{
|
||||
return formatBoolean(b);
|
||||
}
|
||||
|
||||
if(value instanceof LocalTime lt)
|
||||
{
|
||||
return formatLocalTime(lt);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
// if the field has a display format, try to apply it //
|
||||
////////////////////////////////////////////////////////
|
||||
@ -169,6 +170,26 @@ public class QValueFormatter
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String formatDateTimeWithZone(ZonedDateTime dateTime)
|
||||
{
|
||||
return (dateTimeWithZoneFormatter.format(dateTime));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String formatLocalTime(LocalTime localTime)
|
||||
{
|
||||
return (localTimeFormatter.format(localTime));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Make a string from a table's recordLabelFormat and fields, for a given record.
|
||||
*******************************************************************************/
|
||||
@ -261,7 +282,8 @@ public class QValueFormatter
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a list of records, set their recordLabels and display values
|
||||
** For a list of records, set their recordLabels and display values - including
|
||||
** record label (e.g., from the table meta data).
|
||||
*******************************************************************************/
|
||||
public static void setDisplayValuesInRecords(QTableMetaData table, List<QRecord> records)
|
||||
{
|
||||
@ -279,6 +301,24 @@ public class QValueFormatter
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a list of records, set their recordLabels and display values
|
||||
*******************************************************************************/
|
||||
public static void setDisplayValuesInRecords(Collection<QFieldMetaData> fields, List<QRecord> records)
|
||||
{
|
||||
if(records == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for(QRecord record : records)
|
||||
{
|
||||
setDisplayValuesInRecord(fields, record);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a list of records, set their display values
|
||||
*******************************************************************************/
|
||||
@ -294,4 +334,25 @@ public class QValueFormatter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String formatBoolean(Boolean b)
|
||||
{
|
||||
if(b == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
else if(b)
|
||||
{
|
||||
return ("Yes");
|
||||
}
|
||||
else
|
||||
{
|
||||
return ("No");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,8 +26,10 @@ import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
|
||||
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.query.QCriteriaOperator;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@ -42,11 +44,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleVal
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import org.apache.commons.lang.NotImplementedException;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -55,7 +56,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class SearchPossibleValueSourceAction
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(SearchPossibleValueSourceAction.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(SearchPossibleValueSourceAction.class);
|
||||
|
||||
private QPossibleValueTranslator possibleValueTranslator;
|
||||
|
||||
@ -105,13 +106,15 @@ public class SearchPossibleValueSourceAction
|
||||
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
|
||||
List<Serializable> matchingIds = new ArrayList<>();
|
||||
|
||||
List<?> inputIdsAsCorrectType = convertInputIdsToEnumIdType(possibleValueSource, input.getIdList());
|
||||
|
||||
for(QPossibleValue<?> possibleValue : possibleValueSource.getEnumValues())
|
||||
{
|
||||
boolean match = false;
|
||||
|
||||
if(input.getIdList() != null)
|
||||
{
|
||||
if(input.getIdList().contains(possibleValue.getId()))
|
||||
if(inputIdsAsCorrectType.contains(possibleValue.getId()))
|
||||
{
|
||||
match = true;
|
||||
}
|
||||
@ -146,6 +149,44 @@ public class SearchPossibleValueSourceAction
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** The input list of ids might come through as a type that isn't the same as
|
||||
** the type of the ids in the enum (e.g., strings from a frontend, integers
|
||||
** in an enum). So, this method looks at the first id in the enum, and then
|
||||
** maps all the inputIds to be of the same type.
|
||||
*******************************************************************************/
|
||||
private List<Object> convertInputIdsToEnumIdType(QPossibleValueSource possibleValueSource, List<Serializable> inputIdList)
|
||||
{
|
||||
List<Object> rs = new ArrayList<>();
|
||||
if(CollectionUtils.nullSafeIsEmpty(inputIdList))
|
||||
{
|
||||
return (rs);
|
||||
}
|
||||
|
||||
Object anIdFromTheEnum = possibleValueSource.getEnumValues().get(0).getId();
|
||||
|
||||
if(anIdFromTheEnum instanceof Integer)
|
||||
{
|
||||
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsInteger(id)));
|
||||
}
|
||||
else if(anIdFromTheEnum instanceof String)
|
||||
{
|
||||
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsString(id)));
|
||||
}
|
||||
else if(anIdFromTheEnum instanceof Boolean)
|
||||
{
|
||||
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsBoolean(id)));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Unexpected type [" + anIdFromTheEnum.getClass().getSimpleName() + "] for ids in enum: " + possibleValueSource.getName());
|
||||
}
|
||||
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -153,8 +194,7 @@ public class SearchPossibleValueSourceAction
|
||||
{
|
||||
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
|
||||
|
||||
QueryInput queryInput = new QueryInput(input.getInstance());
|
||||
queryInput.setSession(input.getSession());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(possibleValueSource.getTableName());
|
||||
|
||||
QTableMetaData table = input.getInstance().getTable(possibleValueSource.getTableName());
|
||||
@ -236,8 +276,12 @@ public class SearchPossibleValueSourceAction
|
||||
{
|
||||
try
|
||||
{
|
||||
// QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
||||
// return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
|
||||
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
||||
List<QPossibleValue<?>> possibleValues = customPossibleValueProvider.search(input);
|
||||
|
||||
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
|
||||
output.setResults(possibleValues);
|
||||
return (output);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.context;
|
||||
|
||||
|
||||
import java.util.Stack;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** record containing the values managed by QContext.
|
||||
*******************************************************************************/
|
||||
public record CapturedContext(QInstance qInstance, QSession qSession, QBackendTransaction qBackendTransaction, Stack<AbstractActionInput> actionStack)
|
||||
{
|
||||
|
||||
}
|
@ -0,0 +1,262 @@
|
||||
/*
|
||||
* 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.context;
|
||||
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.Stack;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A collection of thread-local variables, to define the current context of the
|
||||
** QQQ code that is running. e.g., what QInstance is being used, what QSession
|
||||
** is active, etc.
|
||||
*******************************************************************************/
|
||||
public class QContext
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QContext.class);
|
||||
|
||||
private static ThreadLocal<QInstance> qInstanceThreadLocal = new ThreadLocal<>();
|
||||
private static ThreadLocal<QSession> qSessionThreadLocal = new ThreadLocal<>();
|
||||
private static ThreadLocal<QBackendTransaction> qBackendTransactionThreadLocal = new ThreadLocal<>();
|
||||
private static ThreadLocal<Stack<AbstractActionInput>> actionStackThreadLocal = new ThreadLocal<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** private constructor - class is not meant to be instantiated.
|
||||
*******************************************************************************/
|
||||
private QContext()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Most common method to set or init the context - e.g., set the current thread
|
||||
** with a QInstance and QSession.
|
||||
*******************************************************************************/
|
||||
public static void init(QInstance qInstance, QSession qSession)
|
||||
{
|
||||
init(qInstance, qSession, null, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Full flavor init method - also take a transaction and action input (to seed the stack).
|
||||
*******************************************************************************/
|
||||
public static void init(QInstance qInstance, QSession qSession, QBackendTransaction transaction, AbstractActionInput actionInput)
|
||||
{
|
||||
qInstanceThreadLocal.set(qInstance);
|
||||
qSessionThreadLocal.set(qSession);
|
||||
qBackendTransactionThreadLocal.set(transaction);
|
||||
|
||||
actionStackThreadLocal.set(new Stack<>());
|
||||
if(actionInput != null)
|
||||
{
|
||||
actionStackThreadLocal.get().add(actionInput);
|
||||
}
|
||||
|
||||
if(!qInstance.getHasBeenValidated())
|
||||
{
|
||||
try
|
||||
{
|
||||
new QInstanceValidator().validate(qInstance);
|
||||
}
|
||||
catch(QInstanceValidationException e)
|
||||
{
|
||||
LOG.warn(e);
|
||||
throw (new IllegalArgumentException("QInstance failed validation" + e.getMessage(), e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Init a new thread with the context captured from a different thread. e.g.,
|
||||
** when starting some async task.
|
||||
*******************************************************************************/
|
||||
public static void init(CapturedContext capturedContext)
|
||||
{
|
||||
init(capturedContext.qInstance(), capturedContext.qSession(), capturedContext.qBackendTransaction(), null);
|
||||
actionStackThreadLocal.set(capturedContext.actionStack());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Capture all values from the current thread - meant to be used with the init
|
||||
** overload that takes a CapturedContext, for setting up a child thread.
|
||||
*******************************************************************************/
|
||||
public static CapturedContext capture()
|
||||
{
|
||||
return (new CapturedContext(getQInstance(), getQSession(), getQBackendTransaction(), getActionStack()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Clear all values in the current thread.
|
||||
*******************************************************************************/
|
||||
public static void clear()
|
||||
{
|
||||
qInstanceThreadLocal.remove();
|
||||
qSessionThreadLocal.remove();
|
||||
qBackendTransactionThreadLocal.remove();
|
||||
actionStackThreadLocal.remove();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QInstance getQInstance()
|
||||
{
|
||||
return (qInstanceThreadLocal.get());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QSession getQSession()
|
||||
{
|
||||
return (qSessionThreadLocal.get());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QBackendTransaction getQBackendTransaction()
|
||||
{
|
||||
return (qBackendTransactionThreadLocal.get());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Stack<AbstractActionInput> getActionStack()
|
||||
{
|
||||
return (actionStackThreadLocal.get());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void pushAction(AbstractActionInput action)
|
||||
{
|
||||
if(actionStackThreadLocal.get() == null)
|
||||
{
|
||||
actionStackThreadLocal.set(new Stack<>());
|
||||
}
|
||||
actionStackThreadLocal.get().push(action);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void popAction()
|
||||
{
|
||||
try
|
||||
{
|
||||
actionStackThreadLocal.get().pop();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.debug("Error popping action stack", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setQInstance(QInstance qInstance)
|
||||
{
|
||||
qInstanceThreadLocal.set(qInstance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setQSession(QSession qSession)
|
||||
{
|
||||
qSessionThreadLocal.set(qSession);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void setTransaction(QBackendTransaction transaction)
|
||||
{
|
||||
qBackendTransactionThreadLocal.set(transaction);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static void clearTransaction()
|
||||
{
|
||||
qBackendTransactionThreadLocal.remove();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Optional<AbstractActionInput> getFirstActionInStack()
|
||||
{
|
||||
if(actionStackThreadLocal.get() == null || actionStackThreadLocal.get().isEmpty())
|
||||
{
|
||||
return (Optional.empty());
|
||||
}
|
||||
|
||||
return (Optional.of(actionStackThreadLocal.get().get(0)));
|
||||
}
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.exceptions;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
* Exception thrown doing authentication
|
||||
*
|
||||
*******************************************************************************/
|
||||
public class AccessTokenException extends QAuthenticationException
|
||||
{
|
||||
private Integer statusCode;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor of message
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AccessTokenException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor of message
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AccessTokenException(String message, int statusCode)
|
||||
{
|
||||
super(message);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor of message & cause
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AccessTokenException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor of message & cause
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AccessTokenException(String message, Throwable cause, int statusCode)
|
||||
{
|
||||
super(message, cause);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for statusCode
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getStatusCode()
|
||||
{
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for statusCode
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setStatusCode(Integer statusCode)
|
||||
{
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for statusCode
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AccessTokenException withStatusCode(Integer statusCode)
|
||||
{
|
||||
this.statusCode = statusCode;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -33,6 +33,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.BulkTableActionProcessPermissionChecker;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
@ -59,6 +60,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QMiddlewareTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.delete.BulkDeleteTransformStep;
|
||||
@ -72,8 +74,6 @@ import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwith
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -83,7 +83,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class QInstanceEnricher
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QInstanceEnricher.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(QInstanceEnricher.class);
|
||||
|
||||
private final QInstance qInstance;
|
||||
|
||||
@ -181,6 +181,11 @@ public class QInstanceEnricher
|
||||
if(table.getFields() != null)
|
||||
{
|
||||
table.getFields().values().forEach(this::enrichField);
|
||||
|
||||
for(QMiddlewareTableMetaData middlewareTableMetaData : CollectionUtils.nonNullMap(table.getMiddlewareMetaData()).values())
|
||||
{
|
||||
middlewareTableMetaData.enrich(table);
|
||||
}
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeIsEmpty(table.getSections()))
|
||||
@ -198,6 +203,20 @@ public class QInstanceEnricher
|
||||
}
|
||||
|
||||
enrichPermissionRules(table);
|
||||
enrichAuditRules(table);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void enrichAuditRules(QTableMetaData table)
|
||||
{
|
||||
if(table.getAuditRules() == null && qInstance.getDefaultAuditRules() != null)
|
||||
{
|
||||
table.setAuditRules(qInstance.getDefaultAuditRules());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -313,14 +332,7 @@ public class QInstanceEnricher
|
||||
{
|
||||
if(!StringUtils.hasContent(field.getLabel()))
|
||||
{
|
||||
if(configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels && StringUtils.hasContent(field.getPossibleValueSourceName()) && field.getName() != null && field.getName().endsWith("Id"))
|
||||
{
|
||||
field.setLabel(nameToLabel(field.getName().substring(0, field.getName().length() - 2)));
|
||||
}
|
||||
else
|
||||
{
|
||||
field.setLabel(nameToLabel(field.getName()));
|
||||
}
|
||||
fieldNameToLabel(field);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@ -328,7 +340,7 @@ public class QInstanceEnricher
|
||||
// and that PVS exists in the instance //
|
||||
// and it's a table-type PVS and the table name is set //
|
||||
// and it's a valid table in the instance, and the table is in some app //
|
||||
// and the field doesn't have a LINK adornment //
|
||||
// and the field doesn't (already) have a LINK or CHIP adornment //
|
||||
// then add a link-to-record-from-table adornment to the field. //
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
if(StringUtils.hasContent(field.getPossibleValueSourceName()))
|
||||
@ -341,7 +353,15 @@ public class QInstanceEnricher
|
||||
{
|
||||
if(qInstance.getTable(tableName) != null && doesAnyAppHaveTable(tableName))
|
||||
{
|
||||
if(field.getAdornments() == null || field.getAdornments().stream().noneMatch(a -> AdornmentType.LINK.equals(a.getType())))
|
||||
boolean hasLinkAdornment = false;
|
||||
boolean hasChipAdornment = false;
|
||||
if(field.getAdornments() != null)
|
||||
{
|
||||
hasLinkAdornment = field.getAdornments().stream().anyMatch(a -> AdornmentType.LINK.equals(a.getType()));
|
||||
hasChipAdornment = field.getAdornments().stream().anyMatch(a -> AdornmentType.CHIP.equals(a.getType()));
|
||||
}
|
||||
|
||||
if(!hasLinkAdornment && !hasChipAdornment)
|
||||
{
|
||||
field.withFieldAdornment(new FieldAdornment().withType(AdornmentType.LINK)
|
||||
.withValue(AdornmentType.LinkValues.TO_RECORD_FROM_TABLE, tableName));
|
||||
@ -354,6 +374,23 @@ public class QInstanceEnricher
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void fieldNameToLabel(QFieldMetaData field)
|
||||
{
|
||||
if(configRemoveIdFromNameWhenCreatingPossibleValueFieldLabels && StringUtils.hasContent(field.getPossibleValueSourceName()) && field.getName() != null && field.getName().endsWith("Id"))
|
||||
{
|
||||
field.setLabel(nameToLabel(field.getName().substring(0, field.getName().length() - 2)));
|
||||
}
|
||||
else
|
||||
{
|
||||
field.setLabel(nameToLabel(field.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.instances;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -39,12 +40,14 @@ import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptActionInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
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.QMiddlewareInstanceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeUsage;
|
||||
@ -65,6 +68,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.AssociatedScript;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QFieldSection;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.Tier;
|
||||
@ -76,8 +80,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -92,7 +94,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class QInstanceValidator
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QInstanceValidator.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(QInstanceValidator.class);
|
||||
|
||||
private boolean printWarnings = false;
|
||||
|
||||
@ -142,6 +144,7 @@ public class QInstanceValidator
|
||||
validateQueuesAndProviders(qInstance);
|
||||
validateJoins(qInstance);
|
||||
validateSecurityKeyTypes(qInstance);
|
||||
validateMiddlewareMetaData(qInstance);
|
||||
|
||||
validateUniqueTopLevelNames(qInstance);
|
||||
}
|
||||
@ -160,6 +163,19 @@ public class QInstanceValidator
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateMiddlewareMetaData(QInstance qInstance)
|
||||
{
|
||||
for(QMiddlewareInstanceMetaData middlewareInstanceMetaData : CollectionUtils.nonNullMap(qInstance.getMiddlewareMetaData()).values())
|
||||
{
|
||||
middlewareInstanceMetaData.validate(qInstance, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -442,12 +458,38 @@ public class QInstanceValidator
|
||||
validateAssociatedScripts(table);
|
||||
validateTableCacheOf(qInstance, table);
|
||||
validateTableRecordSecurityLocks(qInstance, table);
|
||||
validateTableAssociations(qInstance, table);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void validateTableAssociations(QInstance qInstance, QTableMetaData table)
|
||||
{
|
||||
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||
{
|
||||
if(assertCondition(StringUtils.hasContent(association.getName()), "missing a name for an Association on table " + table.getName()))
|
||||
{
|
||||
String messageSuffix = " for Association " + association.getName() + " on table " + table.getName();
|
||||
if(assertCondition(StringUtils.hasContent(association.getAssociatedTableName()), "missing associatedTableName" + messageSuffix))
|
||||
{
|
||||
assertCondition(qInstance.getTable(association.getAssociatedTableName()) != null, "unrecognized associatedTableName " + association.getAssociatedTableName() + messageSuffix);
|
||||
}
|
||||
|
||||
if(assertCondition(StringUtils.hasContent(association.getJoinName()), "missing joinName" + messageSuffix))
|
||||
{
|
||||
assertCondition(qInstance.getJoin(association.getJoinName()) != null, "unrecognized joinName " + association.getJoinName() + messageSuffix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -455,6 +497,7 @@ public class QInstanceValidator
|
||||
{
|
||||
String prefix = "Table " + table.getName() + " ";
|
||||
|
||||
RECORD_SECURITY_LOCKS_LOOP:
|
||||
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(table.getRecordSecurityLocks()))
|
||||
{
|
||||
String securityKeyTypeName = recordSecurityLock.getSecurityKeyType();
|
||||
@ -481,21 +524,61 @@ public class QInstanceValidator
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
if(assertCondition(StringUtils.hasContent(fieldName), prefix + "is missing a fieldName") && !hasAnyBadJoins)
|
||||
{
|
||||
List<QueryJoin> joins = new ArrayList<>();
|
||||
for(String joinName : CollectionUtils.nonNullList(recordSecurityLock.getJoinNameChain()))
|
||||
if(fieldName.contains("."))
|
||||
{
|
||||
QJoinMetaData join = qInstance.getJoin(joinName);
|
||||
if(join.getLeftTable().equals(table.getName()))
|
||||
if(assertCondition(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()), prefix + "field name " + fieldName + " looks like a join (has a dot), but no joinNameChain was given."))
|
||||
{
|
||||
joins.add(new QueryJoin(join));
|
||||
}
|
||||
else if(join.getRightTable().equals(table.getName()))
|
||||
{
|
||||
joins.add(new QueryJoin(join.flip()));
|
||||
List<QueryJoin> joins = new ArrayList<>();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ok - so - the join name chain is going to be like this: //
|
||||
// for a table: orderLineItemExtrinsic (that's 2 away from order, where the security field is): //
|
||||
// - securityFieldName = order.clientId //
|
||||
// - joinNameChain = orderJoinOrderLineItem, orderLineItemJoinOrderLineItemExtrinsic //
|
||||
// so - to navigate from the table to the security field, we need to reverse the joinNameChain, //
|
||||
// and step (via tmpTable variable) back to the securityField //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
ArrayList<String> joinNameChain = new ArrayList<>(CollectionUtils.nonNullList(recordSecurityLock.getJoinNameChain()));
|
||||
Collections.reverse(joinNameChain);
|
||||
|
||||
QTableMetaData tmpTable = table;
|
||||
|
||||
for(String joinName : joinNameChain)
|
||||
{
|
||||
QJoinMetaData join = qInstance.getJoin(joinName);
|
||||
if(join == null)
|
||||
{
|
||||
errors.add(prefix + "joinNameChain contained an unrecognized join: " + joinName);
|
||||
continue RECORD_SECURITY_LOCKS_LOOP;
|
||||
}
|
||||
|
||||
if(join.getLeftTable().equals(tmpTable.getName()))
|
||||
{
|
||||
joins.add(new QueryJoin(join));
|
||||
tmpTable = qInstance.getTable(join.getRightTable());
|
||||
}
|
||||
else if(join.getRightTable().equals(tmpTable.getName()))
|
||||
{
|
||||
joins.add(new QueryJoin(join.flip()));
|
||||
tmpTable = qInstance.getTable(join.getLeftTable());
|
||||
}
|
||||
else
|
||||
{
|
||||
errors.add(prefix + "joinNameChain could not be followed through join: " + joinName);
|
||||
continue RECORD_SECURITY_LOCKS_LOOP;
|
||||
}
|
||||
}
|
||||
|
||||
assertCondition(findField(qInstance, table, joins, fieldName), prefix + "has an unrecognized fieldName: " + fieldName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(assertCondition(CollectionUtils.nullSafeIsEmpty(recordSecurityLock.getJoinNameChain()), prefix + "field name " + fieldName + " does not look like a join (does not have a dot), but a joinNameChain was given."))
|
||||
{
|
||||
assertNoException(() -> table.getField(fieldName), prefix + "has an unrecognized fieldName: " + fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
assertCondition(findField(qInstance, table, joins, fieldName), prefix + "has an unrecognized fieldName: " + fieldName);
|
||||
}
|
||||
|
||||
assertCondition(recordSecurityLock.getNullValueBehavior() != null, prefix + "is missing a nullValueBehavior");
|
||||
@ -888,7 +971,8 @@ public class QInstanceValidator
|
||||
//////////////////////////////////////////
|
||||
// otherwise, just append the exception //
|
||||
//////////////////////////////////////////
|
||||
errors.add(prefix + ": " + e);
|
||||
e.printStackTrace();
|
||||
errors.add(prefix + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,11 +29,10 @@ import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import io.github.cdimascio.dotenv.Dotenv;
|
||||
import io.github.cdimascio.dotenv.DotenvEntry;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -49,7 +48,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class QMetaDataVariableInterpreter
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QMetaDataVariableInterpreter.class);
|
||||
private static final QLogger LOG = QLogger.getLogger(QMetaDataVariableInterpreter.class);
|
||||
|
||||
private Map<String, String> environmentOverrides;
|
||||
private Map<String, Map<String, Serializable>> valueMaps;
|
||||
|
@ -0,0 +1,275 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import com.amazonaws.auth.AWSStaticCredentialsProvider;
|
||||
import com.amazonaws.auth.BasicAWSCredentials;
|
||||
import com.amazonaws.services.secretsmanager.AWSSecretsManager;
|
||||
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder;
|
||||
import com.amazonaws.services.secretsmanager.model.CreateSecretRequest;
|
||||
import com.amazonaws.services.secretsmanager.model.Filter;
|
||||
import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest;
|
||||
import com.amazonaws.services.secretsmanager.model.GetSecretValueResult;
|
||||
import com.amazonaws.services.secretsmanager.model.ListSecretsRequest;
|
||||
import com.amazonaws.services.secretsmanager.model.ListSecretsResult;
|
||||
import com.amazonaws.services.secretsmanager.model.PutSecretValueRequest;
|
||||
import com.amazonaws.services.secretsmanager.model.ResourceExistsException;
|
||||
import com.amazonaws.services.secretsmanager.model.SecretListEntry;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class for working with AWS Secrets Manager.
|
||||
**
|
||||
** Relies on environment variables:
|
||||
** SECRETS_MANAGER_ACCESS_KEY
|
||||
** SECRETS_MANAGER_SECRET_KEY
|
||||
** SECRETS_MANAGER_REGION
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class SecretsManagerUtils
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(SecretsManagerUtils.class);
|
||||
|
||||
private static QMetaDataVariableInterpreter qMetaDataVariableInterpreter;
|
||||
private static AWSSecretsManager _client = null;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** IF secret manager ENV vars are set,
|
||||
** THEN lookup all secrets starting with the given prefix,
|
||||
** and write them to a .env file (backing up any pre-existing .env files first).
|
||||
*******************************************************************************/
|
||||
public static void writeEnvFromSecretsWithNamePrefix(String prefix) throws IOException
|
||||
{
|
||||
Optional<AWSSecretsManager> optionalSecretsManagerClient = getSecretsManagerClient();
|
||||
if(optionalSecretsManagerClient.isPresent())
|
||||
{
|
||||
AWSSecretsManager client = optionalSecretsManagerClient.get();
|
||||
|
||||
ListSecretsRequest listSecretsRequest = new ListSecretsRequest().withFilters(new Filter().withKey("name").withValues(prefix));
|
||||
listSecretsRequest.withMaxResults(100);
|
||||
ListSecretsResult listSecretsResult = client.listSecrets(listSecretsRequest);
|
||||
|
||||
StringBuilder fullEnv = new StringBuilder();
|
||||
while(true)
|
||||
{
|
||||
for(SecretListEntry secretListEntry : listSecretsResult.getSecretList())
|
||||
{
|
||||
String nameWithoutPrefix = secretListEntry.getName().replace(prefix, "");
|
||||
Optional<String> secretValue = getSecret(prefix, nameWithoutPrefix);
|
||||
if(secretValue.isPresent())
|
||||
{
|
||||
String envLine = nameWithoutPrefix + "=" + secretValue.get();
|
||||
fullEnv.append(envLine).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
if(listSecretsResult.getNextToken() != null)
|
||||
{
|
||||
LOG.trace("Calling for next token...");
|
||||
listSecretsRequest.setNextToken(listSecretsResult.getNextToken());
|
||||
listSecretsResult = client.listSecrets(listSecretsRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
File dotEnv = new File(".env");
|
||||
if(dotEnv.exists())
|
||||
{
|
||||
dotEnv.renameTo(new File(".env.backup-" + System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
FileUtils.writeStringToFile(dotEnv, fullEnv.toString());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.info("Not writing .env from secrets manager");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Get a single secret value.
|
||||
**
|
||||
** The lookup in secrets manager is done by (path + name). Then, in the value
|
||||
** that comes back, if it looks like JSON, we look for a value inside it under
|
||||
** the key of just "name". Else, if we didn't get JSON back, then we just return
|
||||
** the full text value of the secret.
|
||||
*******************************************************************************/
|
||||
public static Optional<String> getSecret(String path, String name)
|
||||
{
|
||||
Optional<AWSSecretsManager> optionalSecretsManagerClient = getSecretsManagerClient();
|
||||
if(optionalSecretsManagerClient.isPresent())
|
||||
{
|
||||
try
|
||||
{
|
||||
AWSSecretsManager client = optionalSecretsManagerClient.get();
|
||||
String secretId = path + name;
|
||||
GetSecretValueRequest getSecretValueRequest = new GetSecretValueRequest().withSecretId(secretId);
|
||||
|
||||
GetSecretValueResult getSecretValueResult = client.getSecretValue(getSecretValueRequest);
|
||||
|
||||
try
|
||||
{
|
||||
JSONObject secretJSON = new JSONObject(getSecretValueResult.getSecretString());
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// know we know it's a json object - so - commit to either returning the value under this name, else warning and returning empty //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(secretJSON.has(name))
|
||||
{
|
||||
return (Optional.of(secretJSON.getString(name)));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("SecretsManager secret at [" + secretId + "] was a JSON object, but it did not contain a key of [" + name + "] - so returning empty.");
|
||||
return (Optional.empty());
|
||||
}
|
||||
}
|
||||
catch(JSONException je)
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if the secret value couldn't be parsed as json, then assume it to be text and just return it //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (Optional.of(getSecretValueResult.getSecretString()));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.debug("Error getting secret from secretManager: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
return (Optional.empty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Tries to do a Create - if that fails, then does a Put (update).
|
||||
**
|
||||
** Path is expected to end in a /, but I suppose it isn't strictly required.
|
||||
*******************************************************************************/
|
||||
public static void writeSecret(String path, String name, String value)
|
||||
{
|
||||
JSONObject secretJson = new JSONObject();
|
||||
secretJson.put(name, value);
|
||||
|
||||
Optional<AWSSecretsManager> optionalSecretsManagerClient = getSecretsManagerClient();
|
||||
if(optionalSecretsManagerClient.isPresent())
|
||||
{
|
||||
AWSSecretsManager client = optionalSecretsManagerClient.get();
|
||||
|
||||
try
|
||||
{
|
||||
CreateSecretRequest createSecretRequest = new CreateSecretRequest();
|
||||
createSecretRequest.setName(path + name);
|
||||
createSecretRequest.setSecretString(secretJson.toString());
|
||||
client.createSecret(createSecretRequest);
|
||||
}
|
||||
catch(ResourceExistsException e)
|
||||
{
|
||||
PutSecretValueRequest putSecretValueRequest = new PutSecretValueRequest();
|
||||
putSecretValueRequest.setSecretId(path + name);
|
||||
putSecretValueRequest.setSecretString(secretJson.toString());
|
||||
client.putSecretValue(putSecretValueRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static Optional<AWSSecretsManager> getSecretsManagerClient()
|
||||
{
|
||||
if(_client == null)
|
||||
{
|
||||
QMetaDataVariableInterpreter interpreter = getQMetaDataVariableInterpreter();
|
||||
|
||||
String accessKey = interpreter.interpret("${env.SECRETS_MANAGER_ACCESS_KEY}");
|
||||
String secretKey = interpreter.interpret("${env.SECRETS_MANAGER_SECRET_KEY}");
|
||||
String region = interpreter.interpret("${env.SECRETS_MANAGER_REGION}");
|
||||
|
||||
if(StringUtils.hasContent(accessKey) && StringUtils.hasContent(secretKey) && StringUtils.hasContent(region))
|
||||
{
|
||||
try
|
||||
{
|
||||
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
|
||||
_client = AWSSecretsManagerClientBuilder.standard()
|
||||
.withCredentials(new AWSStaticCredentialsProvider(credentials))
|
||||
.withRegion(region)
|
||||
.build();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error opening Secrets Manager client", e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("One or more SECRETS_MANAGER env var was not set. Unable to open Secrets Manager client.");
|
||||
}
|
||||
}
|
||||
|
||||
return (Optional.ofNullable(_client));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static QMetaDataVariableInterpreter getQMetaDataVariableInterpreter()
|
||||
{
|
||||
return Objects.requireNonNullElseGet(qMetaDataVariableInterpreter, QMetaDataVariableInterpreter::new);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Ideally meant for tests or one-offs to set up a variable interpreter with
|
||||
** an override ENV.
|
||||
*******************************************************************************/
|
||||
static void setQMetaDataVariableInterpreter(QMetaDataVariableInterpreter qMetaDataVariableInterpreter)
|
||||
{
|
||||
SecretsManagerUtils.qMetaDataVariableInterpreter = qMetaDataVariableInterpreter;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,170 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.logging;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeSupplier;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class LogPair
|
||||
{
|
||||
private String key;
|
||||
private Object value;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public LogPair(String key, Object value)
|
||||
{
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
String valueString = getValueString(value);
|
||||
|
||||
return "\"" + Objects.requireNonNullElse(key, "null").replace('"', '.') + "\":" + valueString;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String getValueString(Object value)
|
||||
{
|
||||
String valueString;
|
||||
if(value == null)
|
||||
{
|
||||
valueString = "null";
|
||||
}
|
||||
else if(value instanceof LogPair subLogPair)
|
||||
{
|
||||
valueString = '{' + subLogPair.toString() + '}';
|
||||
}
|
||||
else if(value instanceof LogPair[] subLogPairs)
|
||||
{
|
||||
String subLogPairsString = Arrays.stream(subLogPairs).map(LogPair::toString).collect(Collectors.joining(","));
|
||||
valueString = '{' + subLogPairsString + '}';
|
||||
}
|
||||
else if(value instanceof UnsafeSupplier<?, ?> us)
|
||||
{
|
||||
try
|
||||
{
|
||||
Object o = us.get();
|
||||
return getValueString(o);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
valueString = "LogValueError";
|
||||
}
|
||||
}
|
||||
else if(value instanceof Number n)
|
||||
{
|
||||
valueString = String.valueOf(n);
|
||||
}
|
||||
else
|
||||
{
|
||||
valueString = '"' + String.valueOf(value).replace("\"", "\\\"") + '"';
|
||||
}
|
||||
return valueString;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for key
|
||||
*******************************************************************************/
|
||||
public String getKey()
|
||||
{
|
||||
return (this.key);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for key
|
||||
*******************************************************************************/
|
||||
public void setKey(String key)
|
||||
{
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for key
|
||||
*******************************************************************************/
|
||||
public LogPair withKey(String key)
|
||||
{
|
||||
this.key = key;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for value
|
||||
*******************************************************************************/
|
||||
public Object getValue()
|
||||
{
|
||||
return (this.value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for value
|
||||
*******************************************************************************/
|
||||
public void setValue(Object value)
|
||||
{
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for value
|
||||
*******************************************************************************/
|
||||
public LogPair withValue(Object value)
|
||||
{
|
||||
this.value = value;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.logging;
|
||||
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeSupplier;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class LogUtils
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String jsonLog(List<LogPair> logPairs)
|
||||
{
|
||||
List<LogPair> filteredList = logPairs.stream().filter(Objects::nonNull).toList();
|
||||
if(filteredList.isEmpty())
|
||||
{
|
||||
if(QLogger.processTagLogPairJson != null)
|
||||
{
|
||||
return ("{" + QLogger.processTagLogPairJson + "}");
|
||||
}
|
||||
else
|
||||
{
|
||||
return ("{}");
|
||||
}
|
||||
}
|
||||
|
||||
return ('{' + filteredList.stream().map(LogPair::toString).collect(Collectors.joining(","))
|
||||
+ (QLogger.processTagLogPairJson != null ? (',' + QLogger.processTagLogPairJson) : "") + '}');
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String jsonLog(LogPair... logPairs)
|
||||
{
|
||||
if(logPairs == null || logPairs.length == 0)
|
||||
{
|
||||
return ("{}");
|
||||
}
|
||||
|
||||
return (jsonLog(Arrays.asList(logPairs)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static LogPair logPair(String key, Object value)
|
||||
{
|
||||
return (new LogPair(key, value));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static LogPair logPair(String key, UnsafeSupplier<Object, Exception> valueSupplier)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (new LogPair(key, valueSupplier.get()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
return (new LogPair(key, "exceptionLoggingValue: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static LogPair logPair(String key, LogPair... values)
|
||||
{
|
||||
return (new LogPair(key, values));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
static String filterStackTrace(String stackTrace)
|
||||
{
|
||||
try
|
||||
{
|
||||
String packagesToKeep = "com.kingsrook|com.nutrifresh"; // todo - parameterize!!
|
||||
StringBuilder rs = new StringBuilder();
|
||||
String[] lines = stackTrace.split("\n");
|
||||
|
||||
int indexWithinSubStack = 0;
|
||||
int skipsInThisPackage = 0;
|
||||
String packageBeingSkipped = null;
|
||||
|
||||
for(String line : lines)
|
||||
{
|
||||
boolean keepLine = true;
|
||||
|
||||
if(line.matches("^\\s+at .*"))
|
||||
{
|
||||
keepLine = false;
|
||||
indexWithinSubStack++;
|
||||
if(line.matches("^\\s+at (" + packagesToKeep + ").*"))
|
||||
{
|
||||
keepLine = true;
|
||||
}
|
||||
if(indexWithinSubStack == 1)
|
||||
{
|
||||
keepLine = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
indexWithinSubStack = 0;
|
||||
|
||||
if(skipsInThisPackage > 0)
|
||||
{
|
||||
rs.append("\t... ").append(skipsInThisPackage).append(" in ").append(packageBeingSkipped).append("\n");
|
||||
skipsInThisPackage = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(keepLine)
|
||||
{
|
||||
rs.append(line).append("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
String thisPackage = line.replaceFirst("\\s+at ", "").replaceFirst("(\\w+\\.\\w+).*", "$1");
|
||||
if(Objects.equals(thisPackage, packageBeingSkipped))
|
||||
{
|
||||
skipsInThisPackage++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if(skipsInThisPackage > 0)
|
||||
{
|
||||
rs.append("\t... ").append(skipsInThisPackage).append(" in ").append(packageBeingSkipped).append("\n");
|
||||
}
|
||||
skipsInThisPackage = 1;
|
||||
}
|
||||
packageBeingSkipped = thisPackage;
|
||||
}
|
||||
}
|
||||
|
||||
if(rs.length() > 0)
|
||||
{
|
||||
rs.deleteCharAt(rs.length() - 1);
|
||||
}
|
||||
|
||||
return (rs.toString());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// upon any exception, just return the input //
|
||||
///////////////////////////////////////////////
|
||||
return (stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,575 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.logging;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Wrapper for
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QLogger
|
||||
{
|
||||
private static Map<String, QLogger> loggerMap = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
private static boolean logSessionIdEnabled = true;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// note - read in LogUtils, where log pairs are made into a string. //
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
static String processTagLogPairJson = null;
|
||||
|
||||
private Logger logger;
|
||||
|
||||
static
|
||||
{
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// read the property to see if sessionIds in log messages is enabled, just once, statically //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
try
|
||||
{
|
||||
String propertyName = "qqq.logger.logSessionId.disabled";
|
||||
String propertyValue = System.getProperty(propertyName, "");
|
||||
if(propertyValue.equals("true"))
|
||||
{
|
||||
logSessionIdEnabled = false;
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// read the property (or env var) to see if there's a "processTag" to put on all messages //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
try
|
||||
{
|
||||
String processTag = System.getProperty("qqq.logger.processTag");
|
||||
if(processTag == null)
|
||||
{
|
||||
processTag = new QMetaDataVariableInterpreter().interpret("${env.QQQ_LOGGER_PROCESS_TAG}");
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(processTag))
|
||||
{
|
||||
processTagLogPairJson = "\"processTag\":\"" + processTag + "\"";
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QLogger(Logger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QLogger getLogger(Class<?> c)
|
||||
{
|
||||
return (loggerMap.computeIfAbsent(c.getName(), x -> new QLogger(LogManager.getLogger(c))));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void log(Level level, String message)
|
||||
{
|
||||
logger.log(level, makeJsonString(message));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void log(Level level, String message, Throwable t)
|
||||
{
|
||||
logger.log(level, makeJsonString(message, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void log(Level level, Throwable t)
|
||||
{
|
||||
logger.log(level, makeJsonString(null, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void trace(String message)
|
||||
{
|
||||
logger.trace(makeJsonString(message));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void trace(String message, LogPair... logPairs)
|
||||
{
|
||||
logger.trace(makeJsonString(message, null, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void trace(String message, Object... values)
|
||||
{
|
||||
logger.trace(makeJsonString(message), values);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void trace(String message, Throwable t)
|
||||
{
|
||||
logger.trace(makeJsonString(message, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void trace(String message, Throwable t, LogPair... logPairs)
|
||||
{
|
||||
logger.trace(makeJsonString(message, t, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void trace(Throwable t)
|
||||
{
|
||||
logger.trace(makeJsonString(null, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void debug(String message)
|
||||
{
|
||||
logger.debug(makeJsonString(message));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void debug(String message, LogPair... logPairs)
|
||||
{
|
||||
logger.debug(makeJsonString(message, null, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void debug(String message, Object... values)
|
||||
{
|
||||
logger.debug(makeJsonString(message), values);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void debug(String message, Throwable t)
|
||||
{
|
||||
logger.debug(makeJsonString(message, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void debug(String message, Throwable t, LogPair... logPairs)
|
||||
{
|
||||
logger.debug(makeJsonString(message, t, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void debug(Throwable t)
|
||||
{
|
||||
logger.debug(makeJsonString(null, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void info(String message)
|
||||
{
|
||||
logger.info(makeJsonString(message));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void info(LogPair... logPairs)
|
||||
{
|
||||
logger.info(makeJsonString(null, null, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void info(List<LogPair> logPairList)
|
||||
{
|
||||
logger.info(makeJsonString(null, null, logPairList));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void info(String message, LogPair... logPairs)
|
||||
{
|
||||
logger.info(makeJsonString(message, null, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void info(String message, Object... values)
|
||||
{
|
||||
logger.info(makeJsonString(message), values);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void info(String message, Throwable t)
|
||||
{
|
||||
logger.info(makeJsonString(message, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void info(String message, Throwable t, LogPair... logPairs)
|
||||
{
|
||||
logger.info(makeJsonString(message, t, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void info(Throwable t)
|
||||
{
|
||||
logger.info(makeJsonString(null, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void warn(String message)
|
||||
{
|
||||
logger.warn(makeJsonString(message));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void warn(String message, LogPair... logPairs)
|
||||
{
|
||||
logger.warn(makeJsonString(message, null, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void warn(String message, Object... values)
|
||||
{
|
||||
logger.warn(makeJsonString(message), values);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void warn(String message, Throwable t)
|
||||
{
|
||||
logger.warn(makeJsonString(message, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void warn(String message, Throwable t, LogPair... logPairs)
|
||||
{
|
||||
logger.warn(makeJsonString(message, t, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void warn(Throwable t)
|
||||
{
|
||||
logger.warn(makeJsonString(null, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void error(String message)
|
||||
{
|
||||
logger.error(makeJsonString(message));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void error(String message, LogPair... logPairs)
|
||||
{
|
||||
logger.error(makeJsonString(message, null, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void error(String message, Object... values)
|
||||
{
|
||||
logger.error(makeJsonString(message), values);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void error(String message, Throwable t)
|
||||
{
|
||||
logger.error(makeJsonString(message, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void error(String message, Throwable t, LogPair... logPairs)
|
||||
{
|
||||
logger.error(makeJsonString(message, t, logPairs));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void error(Throwable t)
|
||||
{
|
||||
logger.error(makeJsonString(null, t));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String makeJsonString(String message)
|
||||
{
|
||||
return (makeJsonString(message, null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String makeJsonString(String message, Throwable t)
|
||||
{
|
||||
return (makeJsonString(message, t, (List<LogPair>) null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String makeJsonString(String message, Throwable t, LogPair[] logPairs)
|
||||
{
|
||||
List<LogPair> logPairList = new ArrayList<>();
|
||||
if(logPairs != null)
|
||||
{
|
||||
logPairList.addAll(Arrays.stream(logPairs).toList());
|
||||
}
|
||||
|
||||
return (makeJsonString(message, t, logPairList));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String makeJsonString(String message, Throwable t, List<LogPair> logPairList)
|
||||
{
|
||||
if(logPairList == null)
|
||||
{
|
||||
logPairList = new ArrayList<>();
|
||||
}
|
||||
|
||||
if(StringUtils.hasContent(message))
|
||||
{
|
||||
logPairList.add(0, logPair("message", message));
|
||||
}
|
||||
|
||||
addSessionLogPair(logPairList);
|
||||
|
||||
if(t != null)
|
||||
{
|
||||
logPairList.add(logPair("stackTrace", LogUtils.filterStackTrace(ExceptionUtils.getStackTrace(t))));
|
||||
}
|
||||
|
||||
return (LogUtils.jsonLog(logPairList));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static void addSessionLogPair(List<LogPair> logPairList)
|
||||
{
|
||||
if(logSessionIdEnabled)
|
||||
{
|
||||
QSession session = QContext.getQSession();
|
||||
LogPair sessionLogPair;
|
||||
|
||||
if(session == null)
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// note - being careful here to make the same json structure whether session is known or unknown //
|
||||
// (e.g., not a string in one case and an object in another case) - to help loggly. //
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
sessionLogPair = logPair("session", logPair("id", "unknown"));
|
||||
}
|
||||
else
|
||||
{
|
||||
String user = "unknown";
|
||||
if(session.getUser() != null)
|
||||
{
|
||||
user = session.getUser().getIdReference();
|
||||
}
|
||||
sessionLogPair = logPair("session", logPair("id", session.getUuid()), logPair("user", user));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
logPairList.add(sessionLogPair);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
//////////////////////////////////////
|
||||
// deal with not-modifiable list... //
|
||||
//////////////////////////////////////
|
||||
logPairList = new ArrayList<>(logPairList);
|
||||
logPairList.add(sessionLogPair);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -23,15 +23,16 @@ package com.kingsrook.qqq.backend.core.model.actions;
|
||||
|
||||
|
||||
import java.util.UUID;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
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.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -40,10 +41,7 @@ import org.apache.logging.log4j.Logger;
|
||||
*******************************************************************************/
|
||||
public class AbstractActionInput
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(AbstractActionInput.class);
|
||||
|
||||
protected QInstance instance;
|
||||
protected QSession session;
|
||||
private static final QLogger LOG = QLogger.getLogger(AbstractActionInput.class);
|
||||
|
||||
private AsyncJobCallback asyncJobCallback;
|
||||
|
||||
@ -58,30 +56,9 @@ public class AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AbstractActionInput(QInstance instance)
|
||||
{
|
||||
this.instance = instance;
|
||||
validateInstance(instance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AbstractActionInput(QInstance instance, QSession session)
|
||||
{
|
||||
this(instance);
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** performance instance validation (if not previously done).
|
||||
* // todo - verify this is happening (e.g., when context is set i guess)
|
||||
*******************************************************************************/
|
||||
private void validateInstance(QInstance instance)
|
||||
{
|
||||
@ -108,9 +85,10 @@ public class AbstractActionInput
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
public QAuthenticationMetaData getAuthenticationMetaData()
|
||||
{
|
||||
return (instance.getAuthentication());
|
||||
return (getInstance().getAuthentication());
|
||||
}
|
||||
|
||||
|
||||
@ -119,21 +97,10 @@ public class AbstractActionInput
|
||||
** Getter for instance
|
||||
**
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
public QInstance getInstance()
|
||||
{
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for instance
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setInstance(QInstance instance)
|
||||
{
|
||||
validateInstance(instance);
|
||||
this.instance = instance;
|
||||
return (QContext.getQInstance());
|
||||
}
|
||||
|
||||
|
||||
@ -142,20 +109,10 @@ public class AbstractActionInput
|
||||
** Getter for session
|
||||
**
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
public QSession getSession()
|
||||
{
|
||||
return session;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for session
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setSession(QSession session)
|
||||
{
|
||||
this.session = session;
|
||||
return (QContext.getQSession());
|
||||
}
|
||||
|
||||
|
||||
@ -164,6 +121,7 @@ public class AbstractActionInput
|
||||
** Getter for asyncJobCallback
|
||||
**
|
||||
*******************************************************************************/
|
||||
@JsonIgnore
|
||||
public AsyncJobCallback getAsyncJobCallback()
|
||||
{
|
||||
if(asyncJobCallback == null)
|
||||
@ -194,18 +152,6 @@ public class AbstractActionInput
|
||||
*******************************************************************************/
|
||||
public AbstractActionInput withInstance(QInstance instance)
|
||||
{
|
||||
this.instance = instance;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for session
|
||||
*******************************************************************************/
|
||||
public AbstractActionInput withSession(QSession session)
|
||||
{
|
||||
this.session = session;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
@ -22,10 +22,9 @@
|
||||
package com.kingsrook.qqq.backend.core.model.actions;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
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.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -38,26 +37,6 @@ public class AbstractTableActionInput extends AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QBackendMetaData getBackend()
|
||||
{
|
||||
return (instance.getBackendForTable(getTableName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData getTable()
|
||||
{
|
||||
return (instance.getTable(getTableName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -70,9 +49,19 @@ public class AbstractTableActionInput extends AbstractActionInput
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AbstractTableActionInput(QInstance instance)
|
||||
public QBackendMetaData getBackend()
|
||||
{
|
||||
super(instance);
|
||||
return (QContext.getQInstance().getBackendForTable(getTableName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData getTable()
|
||||
{
|
||||
return (QContext.getQInstance().getTable(getTableName()));
|
||||
}
|
||||
|
||||
|
||||
@ -108,16 +97,4 @@ public class AbstractTableActionInput extends AbstractActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for session
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public AbstractTableActionInput withSession(QSession session)
|
||||
{
|
||||
super.withSession(session);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,95 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.audits;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Input object for the audit action - an object which contains a list of "single"
|
||||
** audit inputs - e.g., the data needed to insert 1 audit.
|
||||
*******************************************************************************/
|
||||
public class AuditInput extends AbstractActionInput implements Serializable
|
||||
{
|
||||
private List<AuditSingleInput> auditSingleInputList = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for auditSingleInputList
|
||||
*******************************************************************************/
|
||||
public List<AuditSingleInput> getAuditSingleInputList()
|
||||
{
|
||||
return (this.auditSingleInputList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for auditSingleInputList
|
||||
*******************************************************************************/
|
||||
public void setAuditSingleInputList(List<AuditSingleInput> auditSingleInputList)
|
||||
{
|
||||
this.auditSingleInputList = auditSingleInputList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for auditSingleInputList
|
||||
*******************************************************************************/
|
||||
public AuditInput withAuditSingleInputList(List<AuditSingleInput> auditSingleInputList)
|
||||
{
|
||||
this.auditSingleInputList = auditSingleInputList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Add a single auditSingleInput
|
||||
*******************************************************************************/
|
||||
public void addAuditSingleInput(AuditSingleInput auditSingleInput)
|
||||
{
|
||||
if(this.auditSingleInputList == null)
|
||||
{
|
||||
this.auditSingleInputList = new ArrayList<>();
|
||||
}
|
||||
this.auditSingleInputList.add(auditSingleInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter to add a single auditSingleInput
|
||||
*******************************************************************************/
|
||||
public AuditInput withAuditSingleInput(AuditSingleInput auditSingleInput)
|
||||
{
|
||||
addAuditSingleInput(auditSingleInput);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.audits;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class AuditOutput extends AbstractActionOutput
|
||||
{
|
||||
}
|
@ -0,0 +1,303 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.audits;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Input data to insert a single audit record (with optional child record)..
|
||||
*******************************************************************************/
|
||||
public class AuditSingleInput
|
||||
{
|
||||
private String auditTableName;
|
||||
private String auditUserName;
|
||||
private Instant timestamp;
|
||||
private String message;
|
||||
private Integer recordId;
|
||||
|
||||
private Map<String, Serializable> securityKeyValues;
|
||||
|
||||
private List<QRecord> details;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for auditTableName
|
||||
*******************************************************************************/
|
||||
public String getAuditTableName()
|
||||
{
|
||||
return (this.auditTableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for auditTableName
|
||||
*******************************************************************************/
|
||||
public void setAuditTableName(String auditTableName)
|
||||
{
|
||||
this.auditTableName = auditTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for auditTableName
|
||||
*******************************************************************************/
|
||||
public AuditSingleInput withAuditTableName(String auditTableName)
|
||||
{
|
||||
this.auditTableName = auditTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for auditUserName
|
||||
*******************************************************************************/
|
||||
public String getAuditUserName()
|
||||
{
|
||||
return (this.auditUserName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for auditUserName
|
||||
*******************************************************************************/
|
||||
public void setAuditUserName(String auditUserName)
|
||||
{
|
||||
this.auditUserName = auditUserName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for auditUserName
|
||||
*******************************************************************************/
|
||||
public AuditSingleInput withAuditUserName(String auditUserName)
|
||||
{
|
||||
this.auditUserName = auditUserName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for timestamp
|
||||
*******************************************************************************/
|
||||
public Instant getTimestamp()
|
||||
{
|
||||
return (this.timestamp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for timestamp
|
||||
*******************************************************************************/
|
||||
public void setTimestamp(Instant timestamp)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for timestamp
|
||||
*******************************************************************************/
|
||||
public AuditSingleInput withTimestamp(Instant timestamp)
|
||||
{
|
||||
this.timestamp = timestamp;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for message
|
||||
*******************************************************************************/
|
||||
public String getMessage()
|
||||
{
|
||||
return (this.message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for message
|
||||
*******************************************************************************/
|
||||
public void setMessage(String message)
|
||||
{
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for message
|
||||
*******************************************************************************/
|
||||
public AuditSingleInput withMessage(String message)
|
||||
{
|
||||
this.message = message;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for securityKeyValues
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getSecurityKeyValues()
|
||||
{
|
||||
return (this.securityKeyValues);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for securityKeyValues
|
||||
*******************************************************************************/
|
||||
public void setSecurityKeyValues(Map<String, Serializable> securityKeyValues)
|
||||
{
|
||||
this.securityKeyValues = securityKeyValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for securityKeyValues
|
||||
*******************************************************************************/
|
||||
public AuditSingleInput withSecurityKeyValues(Map<String, Serializable> securityKeyValues)
|
||||
{
|
||||
this.securityKeyValues = securityKeyValues;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for recordId
|
||||
*******************************************************************************/
|
||||
public Integer getRecordId()
|
||||
{
|
||||
return (this.recordId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for recordId
|
||||
*******************************************************************************/
|
||||
public void setRecordId(Integer recordId)
|
||||
{
|
||||
this.recordId = recordId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for recordId
|
||||
*******************************************************************************/
|
||||
public AuditSingleInput withRecordId(Integer recordId)
|
||||
{
|
||||
this.recordId = recordId;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AuditSingleInput forRecord(QTableMetaData table, QRecord record)
|
||||
{
|
||||
setRecordId(record.getValueInteger(table.getPrimaryKeyField())); // todo support non-integer
|
||||
setAuditTableName(table.getName());
|
||||
|
||||
this.securityKeyValues = new HashMap<>();
|
||||
for(RecordSecurityLock recordSecurityLock : CollectionUtils.nonNullList(table.getRecordSecurityLocks()))
|
||||
{
|
||||
this.securityKeyValues.put(recordSecurityLock.getFieldName(), record.getValueInteger(recordSecurityLock.getFieldName()));
|
||||
}
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for details
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getDetails()
|
||||
{
|
||||
return (this.details);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for details
|
||||
*******************************************************************************/
|
||||
public void setDetails(List<QRecord> details)
|
||||
{
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for details
|
||||
*******************************************************************************/
|
||||
public AuditSingleInput withDetails(List<QRecord> details)
|
||||
{
|
||||
this.details = details;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addDetail(String message)
|
||||
{
|
||||
if(this.details == null)
|
||||
{
|
||||
this.details = new ArrayList<>();
|
||||
}
|
||||
QRecord detail = new QRecord().withValue("message", message);
|
||||
this.details.add(detail);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.audits;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Input object for the DML audit action.
|
||||
*******************************************************************************/
|
||||
public class DMLAuditInput extends AbstractActionInput implements Serializable
|
||||
{
|
||||
private List<QRecord> recordList;
|
||||
private List<QRecord> oldRecordList;
|
||||
private AbstractTableActionInput tableActionInput;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for recordList
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getRecordList()
|
||||
{
|
||||
return (this.recordList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for recordList
|
||||
*******************************************************************************/
|
||||
public void setRecordList(List<QRecord> recordList)
|
||||
{
|
||||
this.recordList = recordList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for recordList
|
||||
*******************************************************************************/
|
||||
public DMLAuditInput withRecordList(List<QRecord> recordList)
|
||||
{
|
||||
this.recordList = recordList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableActionInput
|
||||
*******************************************************************************/
|
||||
public AbstractTableActionInput getTableActionInput()
|
||||
{
|
||||
return (this.tableActionInput);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableActionInput
|
||||
*******************************************************************************/
|
||||
public void setTableActionInput(AbstractTableActionInput tableActionInput)
|
||||
{
|
||||
this.tableActionInput = tableActionInput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableActionInput
|
||||
*******************************************************************************/
|
||||
public DMLAuditInput withTableActionInput(AbstractTableActionInput tableActionInput)
|
||||
{
|
||||
this.tableActionInput = tableActionInput;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for oldRecordList
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getOldRecordList()
|
||||
{
|
||||
return (this.oldRecordList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for oldRecordList
|
||||
*******************************************************************************/
|
||||
public void setOldRecordList(List<QRecord> oldRecordList)
|
||||
{
|
||||
this.oldRecordList = oldRecordList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for oldRecordList
|
||||
*******************************************************************************/
|
||||
public DMLAuditInput withOldRecordList(List<QRecord> oldRecordList)
|
||||
{
|
||||
this.oldRecordList = oldRecordList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2023. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.audits;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Output object for the DML audit action.
|
||||
*******************************************************************************/
|
||||
public class DMLAuditOutput extends AbstractActionOutput implements Serializable
|
||||
{
|
||||
|
||||
}
|
@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.model.actions.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -40,14 +39,4 @@ public class MetaDataInput extends AbstractActionInput
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MetaDataInput(QInstance instance)
|
||||
{
|
||||
super(instance);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.model.actions.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -45,16 +44,6 @@ public class ProcessMetaDataInput extends AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessMetaDataInput(QInstance instance)
|
||||
{
|
||||
super(instance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for processName
|
||||
**
|
||||
|
@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.model.actions.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -40,14 +39,4 @@ public class TableMetaDataInput extends AbstractTableActionInput
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TableMetaDataInput(QInstance instance)
|
||||
{
|
||||
super(instance);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,291 @@
|
||||
/*
|
||||
* 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.actions.processes;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Simple process summary result object, that lets you give a link to a filter
|
||||
** on a table. e.g., if your process built such a records, give a link to it.
|
||||
*******************************************************************************/
|
||||
public class ProcessSummaryFilterLink implements ProcessSummaryLineInterface
|
||||
{
|
||||
private Status status;
|
||||
private String tableName;
|
||||
private QQueryFilter filter;
|
||||
private String linkPreText;
|
||||
private String linkText;
|
||||
private String linkPostText;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryFilterLink()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public LogPair toLogPair()
|
||||
{
|
||||
return (logPair("ProcessSummary", logPair("status", status), logPair("tableName", tableName),
|
||||
logPair("linkPreText", linkPreText), logPair("linkText", linkText), logPair("linkPostText", linkPostText)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryFilterLink(Status status, String tableName, QQueryFilter filter)
|
||||
{
|
||||
this.status = status;
|
||||
this.tableName = tableName;
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryFilterLink(Status status, String tableName, QQueryFilter filter, String linkText)
|
||||
{
|
||||
this.status = status;
|
||||
this.tableName = tableName;
|
||||
this.filter = filter;
|
||||
this.linkText = linkText;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for status
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Status getStatus()
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for status
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setStatus(Status status)
|
||||
{
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for status
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryFilterLink withStatus(Status status)
|
||||
{
|
||||
this.status = status;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getTableName()
|
||||
{
|
||||
return tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryFilterLink withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for linkPreText
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getLinkPreText()
|
||||
{
|
||||
return linkPreText;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for linkPreText
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setLinkPreText(String linkPreText)
|
||||
{
|
||||
this.linkPreText = linkPreText;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for linkPreText
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryFilterLink withLinkPreText(String linkPreText)
|
||||
{
|
||||
this.linkPreText = linkPreText;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for linkText
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getLinkText()
|
||||
{
|
||||
return linkText;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for linkText
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setLinkText(String linkText)
|
||||
{
|
||||
this.linkText = linkText;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for linkText
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryFilterLink withLinkText(String linkText)
|
||||
{
|
||||
this.linkText = linkText;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for linkPostText
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getLinkPostText()
|
||||
{
|
||||
return linkPostText;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for linkPostText
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setLinkPostText(String linkPostText)
|
||||
{
|
||||
this.linkPostText = linkPostText;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for linkPostText
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryFilterLink withLinkPostText(String linkPostText)
|
||||
{
|
||||
this.linkPostText = linkPostText;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filter
|
||||
*******************************************************************************/
|
||||
public QQueryFilter getFilter()
|
||||
{
|
||||
return (this.filter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for filter
|
||||
*******************************************************************************/
|
||||
public void setFilter(QQueryFilter filter)
|
||||
{
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for filter
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryFilterLink withFilter(QQueryFilter filter)
|
||||
{
|
||||
this.filter = filter;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -25,7 +25,9 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -422,6 +424,17 @@ public class ProcessSummaryLine implements ProcessSummaryLineInterface
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public LogPair toLogPair()
|
||||
{
|
||||
return (logPair("ProcessSummary", logPair("status", status), logPair("count", count), logPair("message", message)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for messageSuffix
|
||||
**
|
||||
@ -454,4 +467,28 @@ public class ProcessSummaryLine implements ProcessSummaryLineInterface
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryLine withSingularMessage(String singularMessage)
|
||||
{
|
||||
singularFutureMessage = singularMessage;
|
||||
singularPastMessage = singularMessage;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ProcessSummaryLine withPluralMessage(String pluralMessage)
|
||||
{
|
||||
pluralFutureMessage = pluralMessage;
|
||||
pluralPastMessage = pluralMessage;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -47,4 +48,8 @@ public interface ProcessSummaryLineInterface extends Serializable
|
||||
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
LogPair toLogPair();
|
||||
}
|
||||
|
@ -23,6 +23,8 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -50,6 +52,18 @@ public class ProcessSummaryRecordLink implements ProcessSummaryLineInterface
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public LogPair toLogPair()
|
||||
{
|
||||
return (logPair("ProcessSummary", logPair("status", status), logPair("tableName", tableName), logPair("recordId", recordId),
|
||||
logPair("linkPreText", linkPreText), logPair("linkText", linkText), logPair("linkPostText", linkPostText)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -31,9 +31,9 @@ import java.util.UUID;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
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.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
@ -73,20 +73,8 @@ public class RunBackendStepInput extends AbstractActionInput
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunBackendStepInput(QInstance instance)
|
||||
public RunBackendStepInput(ProcessState processState)
|
||||
{
|
||||
super(instance);
|
||||
processState = new ProcessState();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunBackendStepInput(QInstance instance, ProcessState processState)
|
||||
{
|
||||
super(instance);
|
||||
this.processState = processState;
|
||||
}
|
||||
|
||||
@ -102,7 +90,6 @@ public class RunBackendStepInput extends AbstractActionInput
|
||||
public void cloneFieldsInto(RunBackendStepInput target)
|
||||
{
|
||||
target.setStepName(getStepName());
|
||||
target.setSession(getSession());
|
||||
target.setTableName(getTableName());
|
||||
target.setProcessName(getProcessName());
|
||||
target.setAsyncJobCallback(getAsyncJobCallback());
|
||||
@ -117,7 +104,7 @@ public class RunBackendStepInput extends AbstractActionInput
|
||||
*******************************************************************************/
|
||||
public QStepMetaData getStepMetaData()
|
||||
{
|
||||
return (instance.getProcessStep(getProcessName(), getStepName()));
|
||||
return (QContext.getQInstance().getProcessStep(getProcessName(), getStepName()));
|
||||
}
|
||||
|
||||
|
||||
@ -200,7 +187,7 @@ public class RunBackendStepInput extends AbstractActionInput
|
||||
return (null);
|
||||
}
|
||||
|
||||
return (instance.getTable(tableName));
|
||||
return (QContext.getQInstance().getTable(tableName));
|
||||
}
|
||||
|
||||
|
||||
|
@ -28,6 +28,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.audits.AuditInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
@ -41,6 +42,8 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
|
||||
private ProcessState processState;
|
||||
private Exception exception; // todo - make optional
|
||||
|
||||
private List<AuditInput> auditInputList = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -256,4 +259,35 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
|
||||
this.processState.getRecords().add(record);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for auditInputList
|
||||
*******************************************************************************/
|
||||
public List<AuditInput> getAuditInputList()
|
||||
{
|
||||
return (this.auditInputList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for auditInputList
|
||||
*******************************************************************************/
|
||||
public void setAuditInputList(List<AuditInput> auditInputList)
|
||||
{
|
||||
this.auditInputList = auditInputList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for auditInputList
|
||||
*******************************************************************************/
|
||||
public RunBackendStepOutput withAuditInputList(List<AuditInput> auditInputList)
|
||||
{
|
||||
this.auditInputList = auditInputList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,9 +27,9 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
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.processes.QProcessMetaData;
|
||||
|
||||
|
||||
@ -71,17 +71,6 @@ public class RunProcessInput extends AbstractActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunProcessInput(QInstance instance)
|
||||
{
|
||||
super(instance);
|
||||
processState = new ProcessState();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** e.g., for steps after the first step in a process, seed the data in a run
|
||||
** function request from a process state.
|
||||
@ -99,7 +88,7 @@ public class RunProcessInput extends AbstractActionInput
|
||||
*******************************************************************************/
|
||||
public QProcessMetaData getProcessMetaData()
|
||||
{
|
||||
return (instance.getProcess(getProcessName()));
|
||||
return (QContext.getQInstance().getProcess(getProcessName()));
|
||||
}
|
||||
|
||||
|
||||
|
@ -26,8 +26,6 @@ import java.io.OutputStream;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -56,27 +54,6 @@ public class ExportInput extends AbstractTableActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ExportInput(QInstance instance)
|
||||
{
|
||||
super(instance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ExportInput(QInstance instance, QSession session)
|
||||
{
|
||||
super(instance);
|
||||
setSession(session);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for queryFilter
|
||||
**
|
||||
|
@ -27,8 +27,6 @@ import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -54,27 +52,6 @@ public class ReportInput extends AbstractTableActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ReportInput(QInstance instance)
|
||||
{
|
||||
super(instance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ReportInput(QInstance instance, QSession session)
|
||||
{
|
||||
super(instance);
|
||||
setSession(session);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for reportName
|
||||
**
|
||||
|
@ -27,7 +27,6 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
|
||||
|
||||
@ -47,9 +46,8 @@ public class ExecuteCodeInput extends AbstractActionInput
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ExecuteCodeInput(QInstance qInstance)
|
||||
public ExecuteCodeInput()
|
||||
{
|
||||
super(qInstance);
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,304 @@
|
||||
/*
|
||||
* 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.actions.scripts;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.AdHocScriptCodeReference;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class RunAdHocRecordScriptInput extends AbstractTableActionInput
|
||||
{
|
||||
private AdHocScriptCodeReference codeReference;
|
||||
private Map<String, Serializable> inputValues;
|
||||
private List<Serializable> recordPrimaryKeyList; // can either supply recordList, or recordPrimaryKeyList
|
||||
private List<QRecord> recordList;
|
||||
private String tableName;
|
||||
private QCodeExecutionLoggerInterface logger;
|
||||
|
||||
private Serializable outputObject;
|
||||
|
||||
private Serializable scriptUtils;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunAdHocRecordScriptInput()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for inputValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getInputValues()
|
||||
{
|
||||
return inputValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for inputValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setInputValues(Map<String, Serializable> inputValues)
|
||||
{
|
||||
this.inputValues = inputValues;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for inputValues
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunAdHocRecordScriptInput withInputValues(Map<String, Serializable> inputValues)
|
||||
{
|
||||
this.inputValues = inputValues;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for outputObject
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Serializable getOutputObject()
|
||||
{
|
||||
return outputObject;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for outputObject
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setOutputObject(Serializable outputObject)
|
||||
{
|
||||
this.outputObject = outputObject;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for outputObject
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunAdHocRecordScriptInput withOutputObject(Serializable outputObject)
|
||||
{
|
||||
this.outputObject = outputObject;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for logger
|
||||
*******************************************************************************/
|
||||
public QCodeExecutionLoggerInterface getLogger()
|
||||
{
|
||||
return (this.logger);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for logger
|
||||
*******************************************************************************/
|
||||
public void setLogger(QCodeExecutionLoggerInterface logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for logger
|
||||
*******************************************************************************/
|
||||
public RunAdHocRecordScriptInput withLogger(QCodeExecutionLoggerInterface logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for scriptUtils
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Serializable getScriptUtils()
|
||||
{
|
||||
return scriptUtils;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for scriptUtils
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setScriptUtils(Serializable scriptUtils)
|
||||
{
|
||||
this.scriptUtils = scriptUtils;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for codeReference
|
||||
*******************************************************************************/
|
||||
public AdHocScriptCodeReference getCodeReference()
|
||||
{
|
||||
return (this.codeReference);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for codeReference
|
||||
*******************************************************************************/
|
||||
public void setCodeReference(AdHocScriptCodeReference codeReference)
|
||||
{
|
||||
this.codeReference = codeReference;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for codeReference
|
||||
*******************************************************************************/
|
||||
public RunAdHocRecordScriptInput withCodeReference(AdHocScriptCodeReference codeReference)
|
||||
{
|
||||
this.codeReference = codeReference;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** 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 RunAdHocRecordScriptInput withTableName(String tableName)
|
||||
{
|
||||
this.tableName = tableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for recordList
|
||||
*******************************************************************************/
|
||||
public List<QRecord> getRecordList()
|
||||
{
|
||||
return (this.recordList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for recordList
|
||||
*******************************************************************************/
|
||||
public void setRecordList(List<QRecord> recordList)
|
||||
{
|
||||
this.recordList = recordList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for recordList
|
||||
*******************************************************************************/
|
||||
public RunAdHocRecordScriptInput withRecordList(List<QRecord> recordList)
|
||||
{
|
||||
this.recordList = recordList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for recordPrimaryKeyList
|
||||
*******************************************************************************/
|
||||
public List<Serializable> getRecordPrimaryKeyList()
|
||||
{
|
||||
return (this.recordPrimaryKeyList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for recordPrimaryKeyList
|
||||
*******************************************************************************/
|
||||
public void setRecordPrimaryKeyList(List<Serializable> recordPrimaryKeyList)
|
||||
{
|
||||
this.recordPrimaryKeyList = recordPrimaryKeyList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for recordPrimaryKeyList
|
||||
*******************************************************************************/
|
||||
public RunAdHocRecordScriptInput withRecordPrimaryKeyList(List<Serializable> recordPrimaryKeyList)
|
||||
{
|
||||
this.recordPrimaryKeyList = recordPrimaryKeyList;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -19,31 +19,74 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.utils;
|
||||
package com.kingsrook.qqq.backend.core.model.actions.scripts;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Utility class for logging
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QLogger
|
||||
public class RunAdHocRecordScriptOutput extends AbstractActionOutput
|
||||
{
|
||||
private static Map<String, QLogger> loggerMap = new HashMap<>();
|
||||
private Logger logger;
|
||||
private Serializable output;
|
||||
private QCodeExecutionLoggerInterface logger;
|
||||
private Optional<Exception> exception = Optional.empty();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for output
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QLogger(Logger logger)
|
||||
public Serializable getOutput()
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for output
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setOutput(Serializable output)
|
||||
{
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for output
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunAdHocRecordScriptOutput withOutput(Serializable output)
|
||||
{
|
||||
this.output = output;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for logger
|
||||
*******************************************************************************/
|
||||
public QCodeExecutionLoggerInterface getLogger()
|
||||
{
|
||||
return (this.logger);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for logger
|
||||
*******************************************************************************/
|
||||
public void setLogger(QCodeExecutionLoggerInterface logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
@ -51,88 +94,43 @@ public class QLogger
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Fluent setter for logger
|
||||
*******************************************************************************/
|
||||
public static QLogger getLogger(Class<?> c)
|
||||
public RunAdHocRecordScriptOutput withLogger(QCodeExecutionLoggerInterface logger)
|
||||
{
|
||||
return (loggerMap.computeIfAbsent(c.getName(), x -> new QLogger(LogManager.getLogger(c))));
|
||||
this.logger = logger;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Getter for exception
|
||||
*******************************************************************************/
|
||||
public void debug(QSession session, String message)
|
||||
public Optional<Exception> getException()
|
||||
{
|
||||
logger.debug(wrapMessage(session, message));
|
||||
return (this.exception);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Setter for exception
|
||||
*******************************************************************************/
|
||||
public void info(QSession session, String message)
|
||||
public void setException(Optional<Exception> exception)
|
||||
{
|
||||
logger.info(wrapMessage(session, message));
|
||||
this.exception = exception;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Fluent setter for exception
|
||||
*******************************************************************************/
|
||||
public void warn(QSession session, String message)
|
||||
public RunAdHocRecordScriptOutput withException(Optional<Exception> exception)
|
||||
{
|
||||
logger.warn(wrapMessage(session, message));
|
||||
this.exception = exception;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void warn(QSession session, String message, Throwable t)
|
||||
{
|
||||
logger.warn(wrapMessage(session, message), t);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void error(QSession session, String message)
|
||||
{
|
||||
logger.error(wrapMessage(session, message));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void error(QSession session, String message, Throwable t)
|
||||
{
|
||||
logger.error(wrapMessage(session, message), t);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String wrapMessage(QSession session, String message)
|
||||
{
|
||||
String propertyName = "qqq.logger.logSessionId.disabled";
|
||||
String propertyValue = System.getProperty(propertyName, "");
|
||||
if(propertyValue.equals("true"))
|
||||
{
|
||||
return (message);
|
||||
}
|
||||
|
||||
String sessionString = (session != null) ? session.getUuid() : "Not provided";
|
||||
return ("Session [" + sessionString + "] | " + message);
|
||||
}
|
||||
}
|
@ -24,8 +24,8 @@ package com.kingsrook.qqq.backend.core.model.actions.scripts;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.scripts.logging.QCodeExecutionLoggerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.AssociatedScriptCodeReference;
|
||||
|
||||
|
||||
@ -36,17 +36,19 @@ public class RunAssociatedScriptInput extends AbstractTableActionInput
|
||||
{
|
||||
private AssociatedScriptCodeReference codeReference;
|
||||
private Map<String, Serializable> inputValues;
|
||||
private QCodeExecutionLoggerInterface logger;
|
||||
|
||||
private Serializable outputObject;
|
||||
|
||||
private Serializable scriptUtils;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public RunAssociatedScriptInput(QInstance qInstance)
|
||||
public RunAssociatedScriptInput()
|
||||
{
|
||||
super(qInstance);
|
||||
}
|
||||
|
||||
|
||||
@ -151,4 +153,56 @@ public class RunAssociatedScriptInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for logger
|
||||
*******************************************************************************/
|
||||
public QCodeExecutionLoggerInterface getLogger()
|
||||
{
|
||||
return (this.logger);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for logger
|
||||
*******************************************************************************/
|
||||
public void setLogger(QCodeExecutionLoggerInterface logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for logger
|
||||
*******************************************************************************/
|
||||
public RunAssociatedScriptInput withLogger(QCodeExecutionLoggerInterface logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for scriptUtils
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Serializable getScriptUtils()
|
||||
{
|
||||
return scriptUtils;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for scriptUtils
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setScriptUtils(Serializable scriptUtils)
|
||||
{
|
||||
this.scriptUtils = scriptUtils;
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ package com.kingsrook.qqq.backend.core.model.actions.scripts;
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -44,9 +43,8 @@ public class StoreAssociatedScriptInput extends AbstractTableActionInput
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public StoreAssociatedScriptInput(QInstance instance)
|
||||
public StoreAssociatedScriptInput()
|
||||
{
|
||||
super(instance);
|
||||
}
|
||||
|
||||
|
||||
|
@ -25,7 +25,6 @@ package com.kingsrook.qqq.backend.core.model.actions.scripts;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
|
||||
|
||||
@ -42,9 +41,8 @@ public class TestScriptInput extends AbstractTableActionInput
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public TestScriptInput(QInstance qInstance)
|
||||
public TestScriptInput()
|
||||
{
|
||||
super(qInstance);
|
||||
}
|
||||
|
||||
|
||||
|
@ -24,15 +24,18 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.aggregate;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
** Define an "aggregate", e.g., to be selected in an Aggregate action.
|
||||
** Such as SUM(cost).
|
||||
*******************************************************************************/
|
||||
public class Aggregate implements Serializable
|
||||
{
|
||||
private String fieldName;
|
||||
private AggregateOperator operator;
|
||||
private QFieldType fieldType;
|
||||
|
||||
|
||||
|
||||
@ -55,12 +58,14 @@ public class Aggregate implements Serializable
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(o == null || getClass() != o.getClass())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Aggregate aggregate = (Aggregate) o;
|
||||
return Objects.equals(fieldName, aggregate.fieldName) && operator == aggregate.operator;
|
||||
return Objects.equals(fieldName, aggregate.fieldName) && operator == aggregate.operator && fieldType == aggregate.fieldType;
|
||||
}
|
||||
|
||||
|
||||
@ -71,7 +76,7 @@ public class Aggregate implements Serializable
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return Objects.hash(fieldName, operator);
|
||||
return Objects.hash(fieldName, operator, fieldType);
|
||||
}
|
||||
|
||||
|
||||
@ -153,4 +158,35 @@ public class Aggregate implements Serializable
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fieldType
|
||||
*******************************************************************************/
|
||||
public QFieldType getFieldType()
|
||||
{
|
||||
return (this.fieldType);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for fieldType
|
||||
*******************************************************************************/
|
||||
public void setFieldType(QFieldType fieldType)
|
||||
{
|
||||
this.fieldType = fieldType;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for fieldType
|
||||
*******************************************************************************/
|
||||
public Aggregate withFieldType(QFieldType fieldType)
|
||||
{
|
||||
this.fieldType = fieldType;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -39,6 +38,7 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
private QQueryFilter filter;
|
||||
private List<Aggregate> aggregates;
|
||||
private List<GroupBy> groupBys = new ArrayList<>();
|
||||
private Integer limit;
|
||||
|
||||
private List<QueryJoin> queryJoins = null;
|
||||
|
||||
@ -53,16 +53,6 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AggregateInput(QInstance instance)
|
||||
{
|
||||
super(instance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filter
|
||||
**
|
||||
@ -245,4 +235,38 @@ public class AggregateInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for limit
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getLimit()
|
||||
{
|
||||
return limit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for limit
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setLimit(Integer limit)
|
||||
{
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for limit
|
||||
**
|
||||
*******************************************************************************/
|
||||
public AggregateInput withLimit(Integer limit)
|
||||
{
|
||||
this.limit = limit;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,9 +27,33 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.aggregate;
|
||||
*******************************************************************************/
|
||||
public enum AggregateOperator
|
||||
{
|
||||
COUNT,
|
||||
SUM,
|
||||
MIN,
|
||||
MAX,
|
||||
AVG
|
||||
COUNT("COUNT("),
|
||||
COUNT_DISTINCT("COUNT(DISTINCT "),
|
||||
SUM("SUM("),
|
||||
MIN("MIN("),
|
||||
MAX("MAX("),
|
||||
AVG("AVG(");
|
||||
|
||||
private final String sqlPrefix;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
AggregateOperator(String sqlPrefix)
|
||||
{
|
||||
this.sqlPrefix = sqlPrefix;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for sqlPrefix
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getSqlPrefix()
|
||||
{
|
||||
return sqlPrefix;
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,17 @@ public class GroupBy implements Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public GroupBy(QFieldType type, String fieldName)
|
||||
{
|
||||
this.type = type;
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
|
@ -27,7 +27,6 @@ import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -51,16 +50,6 @@ public class CountInput extends AbstractTableActionInput
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public CountInput(QInstance instance)
|
||||
{
|
||||
super(instance);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for filter
|
||||
**
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user