mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-20 14:10:44 +00:00
Compare commits
165 Commits
version-0.
...
feature/we
Author | SHA1 | Date | |
---|---|---|---|
d7867b8d22 | |||
96217c839d | |||
5c02c1fd2e | |||
9b2c281431 | |||
5327424cec | |||
4fd68f9195 | |||
cb6101d0ed | |||
55e372a70f | |||
7b190d810a | |||
1fb509fea1 | |||
786f9ba8df | |||
55905d251d | |||
d23dbac0d9 | |||
962d09b120 | |||
4827669c0a | |||
6efc34b69e | |||
da52fccc86 | |||
efc69fee4b | |||
1808cea5c0 | |||
a7b5e00e27 | |||
685e747a91 | |||
3d6f05e4ea | |||
97883b3e43 | |||
e11a23ccc0 | |||
12383930b0 | |||
cc19268132 | |||
4883514f58 | |||
2ee26b14a9 | |||
e9e029d8e9 | |||
ff4a0b8849 | |||
7089ec92a6 | |||
60c5c11549 | |||
3c765e9e47 | |||
5db8cf9ca1 | |||
ffca465f04 | |||
dfb584b367 | |||
504c53b108 | |||
60096dde93 | |||
3395ee2146 | |||
9949e96832 | |||
868dcf00d7 | |||
ed6825ff05 | |||
e33033fb05 | |||
32fde00b96 | |||
2491523a6b | |||
6d0f5d4fb3 | |||
bc76a7f66f | |||
5045627b18 | |||
af4dd2a771 | |||
595190fd8f | |||
b8191927e8 | |||
182ffe2939 | |||
ce2ca3f413 | |||
625ed5209c | |||
e603818c69 | |||
fa4cf8ca16 | |||
e58190f15d | |||
be16d5f0cf | |||
e5987238e6 | |||
f81b257dd4 | |||
97434ebb66 | |||
1b9d93e924 | |||
78892b3642 | |||
64a405cbf8 | |||
2d89dafdc1 | |||
28b608c814 | |||
9056be056e | |||
a4ffe815b5 | |||
3f75add3ed | |||
6f1e9413f6 | |||
af51641d2a | |||
17eab1f3d4 | |||
2cd96fd4bc | |||
73aaee1960 | |||
fd13b00793 | |||
64278e674b | |||
2fa829658f | |||
8f751d81fe | |||
d42b67582a | |||
942134b4b0 | |||
aca8436c56 | |||
94631585ee | |||
96c539b323 | |||
235cf9e16c | |||
9cf25ed45c | |||
473cc9c0ae | |||
d733ce9566 | |||
491998ec9a | |||
86997528bb | |||
ebd9dc9c2c | |||
12e194fc2e | |||
55d046cd86 | |||
16cedfeb6e | |||
d0508c2568 | |||
7af23e52d6 | |||
133e507c93 | |||
513c8f2efb | |||
2016d0a448 | |||
1c54a9a8ac | |||
a95650a0ce | |||
410175a133 | |||
8f0d117b13 | |||
916c8c3ba6 | |||
aca199e91e | |||
4acc185698 | |||
d033d3f464 | |||
ae4e269b88 | |||
38cdb94876 | |||
e4d52a0443 | |||
116a4e883b | |||
36ff5eea02 | |||
75fdff031a | |||
14398d2c94 | |||
9aa25b4f14 | |||
b863d62688 | |||
08ed9a5aad | |||
244239f053 | |||
0f8ad2fb78 | |||
f99c39e0f6 | |||
2c32c5a9fc | |||
5a5d98a3ff | |||
7d2282ebb7 | |||
8e9954c909 | |||
8cf53e045e | |||
955cb67a2c | |||
45a6c3bcad | |||
d0768a6981 | |||
0c72210e8e | |||
a2b36a10e7 | |||
f92ab85c8c | |||
2c976e59f4 | |||
23e87cd9ce | |||
7c39372153 | |||
491fcd6d25 | |||
e0045bb212 | |||
04e13413ef | |||
a489808847 | |||
1a5a374c4e | |||
f49be5ff63 | |||
a5c65b9e67 | |||
48fbb3d054 | |||
bcca710316 | |||
6d749e9df6 | |||
81ffe1a286 | |||
6b49abb749 | |||
efb47b9cd6 | |||
29f2feb321 | |||
3537d2cfd1 | |||
634abe3822 | |||
93c7fbca25 | |||
ea40197893 | |||
38293b81d7 | |||
7b141c3f5b | |||
502095002c | |||
42a8d37493 | |||
6725704b13 | |||
48ac6a0a4f | |||
3f4d11b22a | |||
f147516e45 | |||
f3fe8a3c73 | |||
71dcf231db | |||
a20efabcf2 | |||
00b72e0338 | |||
b979e6545a | |||
7982cad794 |
@ -91,7 +91,7 @@ And then having a bug in the check permission logic on the _Light Bulb Inventory
|
||||
No!
|
||||
|
||||
All of the (really important, even though application developers hate doing it) aspects of security - you don't need to write ANY code for dealing with that.
|
||||
Just tell QQQ what Authentication provider you want to use (e.g., https://auth0.com/[Auth0]), and - to paraphrase the old https://www.youtube.com/watch?v=YHzM4avGrKI[iMac ad] - there's no step 2.
|
||||
Just tell QQQ what Authentication provider you want to use (e.g., OAuth2 or https://auth0.com/[Auth0]), and - to paraphrase the old https://www.youtube.com/watch?v=YHzM4avGrKI[iMac ad] - there's no step 2.
|
||||
QQQ just does it.
|
||||
|
||||
''''
|
||||
|
@ -31,11 +31,9 @@ include::metaData/PermissionRules.adoc[leveloffset=+1]
|
||||
|
||||
== Services
|
||||
|
||||
include::misc/Javalin.adoc[leveloffset=+1]
|
||||
include::misc/ScheduledJobs.adoc[leveloffset=+1]
|
||||
|
||||
=== Web server (Javalin)
|
||||
#todo#
|
||||
|
||||
=== API server (OpenAPI)
|
||||
#todo#
|
||||
|
||||
|
109
docs/misc/Javalin.adoc
Normal file
109
docs/misc/Javalin.adoc
Normal file
@ -0,0 +1,109 @@
|
||||
== QQQ Middleware: Javalin web server
|
||||
include::../variables.adoc[]
|
||||
|
||||
QQQ provides a standard implementation of a middleware layer - that is - code that exists between the
|
||||
QQQ backend and user interface. This implementation is a web server built using the https://javalin.io/[Javalin framework],
|
||||
packaged and deployed in the `qqq-middleware-javalin` maven module
|
||||
|
||||
The de facto way to create a QQQ application server is to write a class which uses an instance of one of the
|
||||
subclasses of `QApplicationJavalinServer`.
|
||||
|
||||
For example, if your application metadata is defined in a directory of yaml files, your server class could be implemented as:
|
||||
|
||||
[source,java]
|
||||
.ConfigFileBasedQQQApplication usage example
|
||||
----
|
||||
public static void main(String[] args)
|
||||
{
|
||||
try
|
||||
{
|
||||
String path = "src/main/resources/metadata";
|
||||
ConfigFilesBasedQQQApplication application = new ConfigFilesBasedQQQApplication(path);
|
||||
QApplicationJavalinServer javalinServer = new QApplicationJavalinServer(application);
|
||||
javalinServer.start();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Failed to start javalin server. See stack trace for details.", e);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
A similar class exists if your metadata is produced by a package of Java MetaDataProducer objects: `MetaDataProducerBasedQQQApplication`.
|
||||
|
||||
=== QApplicationJavalinServer
|
||||
This class provides the bridge between your QQQ Application (e.g., your metadata) and the QQQ Middleware layer
|
||||
served by a Javalin web server. It has several properties to control behaviors:
|
||||
|
||||
* `Integer port` - (default `8000`) - port to use for serving HTTP.
|
||||
* `boolean serveFrontendMaterialDashboard` - (default `true`) whether to serve the javascript frontend provided
|
||||
in the maven artifact `qqq-frontend-material-dashboard`.
|
||||
* `boolean serveLegacyUnversionedMiddlewareAPI` - (default `true`) whether to serve a version the original implementation
|
||||
of the QQQ middleware, which current version of `qqq-frontend-material-dashboard` are compatible with.
|
||||
* `List<AbstractMiddlewareVersion> middlewareVersionList` - (default contains `MiddlewareVersionV1`) - list of
|
||||
newer, formally versioned implementations of the QQQ middleware interface to be served.
|
||||
* `Consumer<Javalin> javalinConfigurationCustomizer` - (default `null`) - optional hook to customize the
|
||||
javalin service object before it is started.
|
||||
* `List<QJavalinRouteProviderInterface> additionalRouteProviders` - (default `null`) - list of fully custom
|
||||
implementations of `QJavalinRouteProviderInterface`, to add additional endpoints to the javalin server.
|
||||
** _Note, you may first want to consider using JavalinRouteProviderMetaData instead - see below._
|
||||
* `QJavalinMetaData javalinMetaData` - (default `null`) - optional alternative place to define `JavalinMetaData` (vs.
|
||||
defining it in the `QInstance`). _Note that if it is set in both places, the one in the QApplicationJavalinServer
|
||||
is used._
|
||||
|
||||
=== JavalinMetaData
|
||||
Certain behaviors of a QQQ Javalin server are configured in a declarative manner by adding a `QJavalinMetaData`
|
||||
object to the `supplementalMetaData` in your `QInstance` (or, as mentioned above, by setting it directly on the
|
||||
`QApplicationJavalinServer`):
|
||||
|
||||
* `List<JavalinRouteProviderMetaData> routeProviders` - (default `null`) optional list of custom route providers to
|
||||
add to the Javalin server. See below for details.
|
||||
* `String uploadedFileArchiveTableName` - (default `null`) - reference to a QQQ Table in your application instance,
|
||||
needed to support the Bulk Load process, as well as any other processes which need to accept an uploaded file
|
||||
as input.
|
||||
* `boolean loggerDisabled` - (default `false`)
|
||||
* `Function<QJavalinAccessLogger.LogEntry, Boolean> logFilter` - (default `null`)
|
||||
* `boolean queryWithoutLimitAllowed` - (default `false`)
|
||||
* `Integer queryWithoutLimitDefault` - (default `1000`)
|
||||
* `Level queryWithoutLimitLogLevel` - (default `INFO`)
|
||||
|
||||
==== JavalinRouteProviderMetaData
|
||||
This type of metadata allows you to add additional http route providers to your Javalin instance, e.g., for
|
||||
serving static files or for running custom code from your application (in the form of QQQ Processes) to respond
|
||||
to HTTP requests.
|
||||
|
||||
* `String hostedPath` - (required)
|
||||
* `String fileSystemPath` - (required for a static router)
|
||||
* `String processName` - required for a dynamic, process-based router. Must be a process name within the QQQ Instance.
|
||||
See below for additional details
|
||||
* `List<String> methods` - required list of HTTP methods (verbs) that are served by the route provider
|
||||
* `QCodeReference routeAuthenticator - Optional reference to a class that implements `RouteAuthenticatorInterface`,
|
||||
to provide security authentication to all requests handled by the route provider.
|
||||
** A default implementation is provided as `SimpleRouteAuthenticator`, which requires that a user session be present
|
||||
to access paths served by the route provider.
|
||||
|
||||
===== Process-based route provider processes
|
||||
If you define a `JavalinRouteProviderMetaData` with a `processName` (e.g., to serve dynamic HTTP responses from your javalin
|
||||
server), the process that you implement will be called to respond to any HTTP requests received by the javalin
|
||||
server which match the `hostedPath` and `methods` that are specified in the metadata.
|
||||
|
||||
The QQQ javalin server will marshal request data from the javalin context into the process's payload, conforming to
|
||||
the shape of the `ProcessBasedRouterPayload` class. Similarly, the http response will be built by taking values from
|
||||
the process's output/state conforming to the fields in that class. As such, it is recommended to use a
|
||||
`ProcessBasedRouterPayload` instance, as show in this example:
|
||||
|
||||
[source,java]
|
||||
.Process-based router usage example (including ProcessBasedRouterPayload)
|
||||
----
|
||||
public class MyDynamicSiteProcessStep implements BackendStep
|
||||
{
|
||||
@Override
|
||||
public void run(RunBackendStepInput input, RunBackendStepOutput output) throws QException
|
||||
{
|
||||
ProcessBasedRouterPayload payload = input.getProcessPayload(ProcessBasedRouterPayload.class);
|
||||
String path = payload.getPath();
|
||||
payload.setResponseString("You requested: " + path);
|
||||
output.setProcessPayload(payload);
|
||||
}
|
||||
}
|
||||
----
|
2
pom.xml
2
pom.xml
@ -48,7 +48,7 @@
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<revision>0.24.0</revision>
|
||||
<revision>0.26.0-SNAPSHOT</revision>
|
||||
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
|
@ -65,7 +65,11 @@
|
||||
<artifactId>aws-java-sdk-secretsmanager</artifactId>
|
||||
<version>1.12.385</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>77.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
@ -125,10 +129,16 @@
|
||||
<version>2.16.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.nimbusds</groupId>
|
||||
<artifactId>oauth2-oidc-sdk</artifactId>
|
||||
<version>11.23.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>auth0</artifactId>
|
||||
<version>2.1.0</version>
|
||||
<version>2.18.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
@ -138,12 +148,12 @@
|
||||
<dependency>
|
||||
<groupId>com.auth0</groupId>
|
||||
<artifactId>jwks-rsa</artifactId>
|
||||
<version>0.22.0</version>
|
||||
<version>0.22.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.github.cdimascio</groupId>
|
||||
<artifactId>java-dotenv</artifactId>
|
||||
<version>5.2.2</version>
|
||||
<artifactId>dotenv-java</artifactId>
|
||||
<version>3.2.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.velocity</groupId>
|
||||
@ -151,16 +161,21 @@
|
||||
<version>2.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- the next 2 deps are for html to pdf - per https://www.baeldung.com/java-html-to-pdf -->
|
||||
<!-- the next 3 deps are for html to pdf -->
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.15.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xhtmlrenderer</groupId>
|
||||
<artifactId>flying-saucer-pdf-openpdf</artifactId>
|
||||
<version>9.1.22</version>
|
||||
<groupId>com.openhtmltopdf</groupId>
|
||||
<artifactId>openhtmltopdf-core</artifactId>
|
||||
<version>1.0.10</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.openhtmltopdf</groupId>
|
||||
<artifactId>openhtmltopdf-pdfbox</artifactId>
|
||||
<version>1.0.10</version>
|
||||
</dependency>
|
||||
|
||||
<!-- the next 3 deps are being added for google drive support -->
|
||||
|
@ -83,7 +83,7 @@ public class RunRecordScriptAutomationHandler extends RecordAutomationHandler
|
||||
}
|
||||
|
||||
QRecord scriptRevision = queryOutput.getRecords().get(0);
|
||||
LOG.info("Running script against records", logPair("scriptRevisionId", scriptRevision.getValue("id")), logPair("scriptId", scriptRevision.getValue("scriptIdd")));
|
||||
LOG.debug("Running script against records", logPair("scriptRevisionId", scriptRevision.getValue("id")), logPair("scriptId", scriptRevision.getValue("scriptIdd")));
|
||||
|
||||
RunAdHocRecordScriptInput input = new RunAdHocRecordScriptInput();
|
||||
input.setCodeReference(new AdHocScriptCodeReference().withScriptRevisionRecord(scriptRevision));
|
||||
|
@ -649,7 +649,7 @@ public class PollingAutomationPerTableRunner implements Runnable
|
||||
input.setRecordList(records);
|
||||
input.setAction(action);
|
||||
|
||||
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getRecordAutomationHandler(action);
|
||||
RecordAutomationHandler recordAutomationHandler = QCodeLoader.getAdHoc(RecordAutomationHandler.class, action.getCodeReference());
|
||||
recordAutomationHandler.execute(input);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,226 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QueryOrGetInputInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.delete.DeleteInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceWithProperties;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Implementation of TableCustomizerInterface that runs multiple other customizers
|
||||
*******************************************************************************/
|
||||
public class MultiCustomizer implements InitializableViaCodeReference, TableCustomizerInterface
|
||||
{
|
||||
private static final String KEY_CODE_REFERENCES = "codeReferences";
|
||||
|
||||
private List<TableCustomizerInterface> customizers = new ArrayList<>();
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Factory method that builds a {@link QCodeReferenceWithProperties} that will
|
||||
* allow this multi-customizer to be assigned to a table, and to track
|
||||
* in that code ref's properties, the "sub" QCodeReferences to be used.
|
||||
*
|
||||
* Added to a table as in:
|
||||
* <pre>
|
||||
* table.withCustomizer(TableCustomizers.POST_INSERT_RECORD,
|
||||
* MultiCustomizer.of(QCodeReference(x), QCodeReference(y)));
|
||||
* </pre>
|
||||
*
|
||||
* @param codeReferences
|
||||
* one or more {@link QCodeReference objects} to run when this customizer
|
||||
* runs. note that they will run in the order provided in this list.
|
||||
***************************************************************************/
|
||||
public static QCodeReferenceWithProperties of(QCodeReference... codeReferences)
|
||||
{
|
||||
ArrayList<QCodeReference> list = new ArrayList<>(Arrays.stream(codeReferences).toList());
|
||||
return (new QCodeReferenceWithProperties(MultiCustomizer.class, MapBuilder.of(KEY_CODE_REFERENCES, list)));
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Add an additional table customizer code reference to an existing
|
||||
* codeReference, e.g., constructed by the `of` factory method.
|
||||
*
|
||||
* @see #of(QCodeReference...)
|
||||
***************************************************************************/
|
||||
public static void addTableCustomizer(QCodeReferenceWithProperties existingMultiCustomizerCodeReference, QCodeReference codeReference)
|
||||
{
|
||||
ArrayList<QCodeReference> list = (ArrayList<QCodeReference>) existingMultiCustomizerCodeReference.getProperties().computeIfAbsent(KEY_CODE_REFERENCES, key -> new ArrayList<>());
|
||||
list.add(codeReference);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* When this class is instantiated by the QCodeLoader, initialize the
|
||||
* sub-customizer objects.
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void initialize(QCodeReference codeReference)
|
||||
{
|
||||
if(codeReference instanceof QCodeReferenceWithProperties codeReferenceWithProperties)
|
||||
{
|
||||
Serializable codeReferencesPropertyValue = codeReferenceWithProperties.getProperties().get(KEY_CODE_REFERENCES);
|
||||
if(codeReferencesPropertyValue instanceof List<?> list)
|
||||
{
|
||||
for(Object o : list)
|
||||
{
|
||||
if(o instanceof QCodeReference reference)
|
||||
{
|
||||
TableCustomizerInterface customizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, reference);
|
||||
customizers.add(customizer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Property KEY_CODE_REFERENCES [" + KEY_CODE_REFERENCES + "] must be a List<QCodeReference>.");
|
||||
}
|
||||
}
|
||||
|
||||
if(customizers.isEmpty())
|
||||
{
|
||||
LOG.info("No TableCustomizers were specified for MultiCustomizer.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run postQuery method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postQuery(QueryOrGetInputInterface queryInput, List<QRecord> records) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.postQuery(queryInput, records);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run preInsert method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.preInsert(insertInput, records, isPreview);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run postInsert method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.postInsert(insertInput, records);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run preUpdate method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.preUpdate(updateInput, records, isPreview, oldRecordList);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run postUpdate method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.postUpdate(updateInput, records, oldRecordList);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run preDelete method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> preDelete(DeleteInput deleteInput, List<QRecord> records, boolean isPreview) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.preDelete(deleteInput, records, isPreview);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* run postDelete method over all sub-customizers
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QRecord> postDelete(DeleteInput deleteInput, List<QRecord> records) throws QException
|
||||
{
|
||||
for(TableCustomizerInterface customizer : customizers)
|
||||
{
|
||||
records = customizer.postDelete(deleteInput, records);
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.utils.collections.TypeTolerantKeyMap;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** utility class to help table customizers working with the oldRecordList.
|
||||
** Usage is just 2 lines:
|
||||
** outside of loop-over-records:
|
||||
** - OldRecordHelper oldRecordHelper = new OldRecordHelper(updateInput.getTableName(), oldRecordList);
|
||||
** then inside the record loop:
|
||||
** - Optional<QRecord> oldRecord = oldRecordHelper.getOldRecord(record);
|
||||
*******************************************************************************/
|
||||
public class OldRecordHelper
|
||||
{
|
||||
private String primaryKeyField;
|
||||
private QFieldType primaryKeyType;
|
||||
|
||||
private Optional<List<QRecord>> oldRecordList;
|
||||
private Map<Serializable, QRecord> oldRecordMap;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public OldRecordHelper(String tableName, Optional<List<QRecord>> oldRecordList)
|
||||
{
|
||||
this.primaryKeyField = QContext.getQInstance().getTable(tableName).getPrimaryKeyField();
|
||||
this.primaryKeyType = QContext.getQInstance().getTable(tableName).getField(primaryKeyField).getType();
|
||||
|
||||
this.oldRecordList = oldRecordList;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public Optional<QRecord> getOldRecord(QRecord record)
|
||||
{
|
||||
if(oldRecordMap == null)
|
||||
{
|
||||
if(oldRecordList.isPresent())
|
||||
{
|
||||
oldRecordMap = new TypeTolerantKeyMap<>(primaryKeyType);
|
||||
oldRecordList.get().forEach(r -> oldRecordMap.put(r.getValue(primaryKeyField), r));
|
||||
}
|
||||
else
|
||||
{
|
||||
oldRecordMap = Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
return (Optional.ofNullable(oldRecordMap.get(record.getValue(primaryKeyField))));
|
||||
}
|
||||
}
|
@ -24,17 +24,11 @@ package com.kingsrook.qqq.backend.core.actions.customizers;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.TableAutomationAction;
|
||||
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
@ -43,9 +37,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
/*******************************************************************************
|
||||
** Utility to load code for running QQQ customizers.
|
||||
**
|
||||
** TODO - redo all to go through method that memoizes class & constructor
|
||||
** lookup. That memoziation causes 1,000,000 such calls to go from ~500ms
|
||||
** to ~100ms.
|
||||
** That memoization causes 1,000,000 such calls to go from ~500ms to ~100ms.
|
||||
*******************************************************************************/
|
||||
public class QCodeLoader
|
||||
{
|
||||
@ -70,84 +62,6 @@ public class QCodeLoader
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, R> Function<T, R> getFunction(QCodeReference codeReference)
|
||||
{
|
||||
if(codeReference == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Class<?> customizerClass = Class.forName(codeReference.getName());
|
||||
return ((Function<T, R>) customizerClass.getConstructor().newInstance());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||
// as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
|
||||
// if it finds an invalid code reference //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends BackendStep> T getBackendStep(Class<T> expectedType, QCodeReference codeReference)
|
||||
{
|
||||
if(codeReference == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new IllegalArgumentException("Only JAVA BackendSteps are supported at this time."));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Class<?> customizerClass = Class.forName(codeReference.getName());
|
||||
return ((T) customizerClass.getConstructor().newInstance());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||
// as we'll want to validate all functions in the instance validator at startup time (and IT will throw //
|
||||
// if it finds an invalid code reference //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -177,7 +91,17 @@ public class QCodeLoader
|
||||
|
||||
if(constructor.isPresent())
|
||||
{
|
||||
return ((T) constructor.get().newInstance());
|
||||
T t = (T) constructor.get().newInstance();
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// if the object is initializable, then, well, initialize it! //
|
||||
////////////////////////////////////////////////////////////////
|
||||
if(t instanceof InitializableViaCodeReference initializableViaCodeReference)
|
||||
{
|
||||
initializableViaCodeReference.initialize(codeReference);
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -187,7 +111,7 @@ public class QCodeLoader
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error initializing customizer", e, logPair("codeReference", codeReference));
|
||||
LOG.error("Error initializing codeReference", e, logPair("codeReference", codeReference));
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// return null here - under the assumption that during normal run-time operations, we'll never hit here //
|
||||
@ -198,67 +122,4 @@ public class QCodeLoader
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static RecordAutomationHandler getRecordAutomationHandler(TableAutomationAction action) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
QCodeReference codeReference = action.getCodeReference();
|
||||
if(!codeReference.getCodeType().equals(QCodeType.JAVA))
|
||||
{
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - 1) support more languages, 2) wrap them w/ java Functions here, 3) profit! //
|
||||
///////////////////////////////////////////////////////////////////////////////////////
|
||||
throw (new IllegalArgumentException("Only JAVA customizers are supported at this time."));
|
||||
}
|
||||
|
||||
Class<?> codeClass = Class.forName(codeReference.getName());
|
||||
Object codeObject = codeClass.getConstructor().newInstance();
|
||||
if(!(codeObject instanceof RecordAutomationHandler recordAutomationHandler))
|
||||
{
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of RecordAutomationHandler"));
|
||||
}
|
||||
return (recordAutomationHandler);
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
throw (qe);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error getting record automation handler for action [" + action.getName() + "]", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static QCustomPossibleValueProvider getCustomPossibleValueProvider(QPossibleValueSource possibleValueSource) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
Class<?> codeClass = Class.forName(possibleValueSource.getCustomCodeReference().getName());
|
||||
Object codeObject = codeClass.getConstructor().newInstance();
|
||||
if(!(codeObject instanceof QCustomPossibleValueProvider customPossibleValueProvider))
|
||||
{
|
||||
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of QCustomPossibleValueProvider"));
|
||||
}
|
||||
return (customPossibleValueProvider);
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
throw (qe);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error getting custom possible value provider for PVS [" + possibleValueSource.getName() + "]", e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -290,7 +290,18 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "call one that doesn't take input param")
|
||||
public static String linkRecordEdit(AbstractActionInput input, String tableName, Serializable recordId) throws QException
|
||||
{
|
||||
return linkRecordEdit(tableName, recordId);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkRecordEdit(String tableName, Serializable recordId) throws QException
|
||||
{
|
||||
String tablePath = QContext.getQInstance().getTablePath(tableName);
|
||||
return (tablePath + "/" + recordId + "/edit");
|
||||
@ -317,7 +328,17 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "call one that doesn't take input param")
|
||||
public static String linkProcessForFilter(AbstractActionInput input, String processName, QQueryFilter filter) throws QException
|
||||
{
|
||||
return linkProcessForFilter(processName, filter);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkProcessForFilter(String processName, QQueryFilter filter) throws QException
|
||||
{
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
if(process == null)
|
||||
@ -337,10 +358,21 @@ public abstract class AbstractHTMLWidgetRenderer extends AbstractWidgetRenderer
|
||||
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "call one that doesn't take input param")
|
||||
public static String linkProcessForRecord(AbstractActionInput input, String processName, Serializable recordId) throws QException
|
||||
{
|
||||
return linkProcessForRecord(processName, recordId);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static String linkProcessForRecord(String processName, Serializable recordId) throws QException
|
||||
{
|
||||
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
|
||||
String tableName = process.getTableName();
|
||||
|
@ -33,6 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
/*******************************************************************************
|
||||
** a default implementation of MetaDataFilterInterface, that allows all the things
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
public class AllowAllMetaDataFilter implements MetaDataFilterInterface
|
||||
{
|
||||
|
||||
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** a default implementation of MetaDataFilterInterface, that is all noop.
|
||||
*******************************************************************************/
|
||||
public class DefaultNoopMetaDataActionCustomizer implements MetaDataActionCustomizerInterface
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowTable(MetaDataInput input, QTableMetaData table)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowProcess(MetaDataInput input, QProcessMetaData process)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowReport(MetaDataInput input, QReportMetaData report)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowApp(MetaDataInput input, QAppMetaData app)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget)
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
}
|
@ -23,10 +23,12 @@ package com.kingsrook.qqq.backend.core.actions.metadata;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
|
||||
import com.kingsrook.qqq.backend.core.actions.permissions.PermissionCheckResult;
|
||||
@ -34,6 +36,7 @@ import com.kingsrook.qqq.backend.core.actions.permissions.PermissionsHelper;
|
||||
import com.kingsrook.qqq.backend.core.context.QContext;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QUserFacingException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||
@ -65,7 +68,7 @@ public class MetaDataAction
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(MetaDataAction.class);
|
||||
|
||||
private static Memoization<QInstance, MetaDataFilterInterface> metaDataFilterMemoization = new Memoization<>();
|
||||
private static Memoization<QInstance, MetaDataActionCustomizerInterface> metaDataActionCustomizerMemoization = new Memoization<>();
|
||||
|
||||
|
||||
|
||||
@ -79,7 +82,7 @@ public class MetaDataAction
|
||||
MetaDataOutput metaDataOutput = new MetaDataOutput();
|
||||
Map<String, AppTreeNode> treeNodes = new LinkedHashMap<>();
|
||||
|
||||
MetaDataFilterInterface filter = getMetaDataFilter();
|
||||
MetaDataActionCustomizerInterface customizer = getMetaDataActionCustomizer();
|
||||
|
||||
/////////////////////////////////////
|
||||
// map tables to frontend metadata //
|
||||
@ -90,7 +93,7 @@ public class MetaDataAction
|
||||
String tableName = entry.getKey();
|
||||
QTableMetaData table = entry.getValue();
|
||||
|
||||
if(!filter.allowTable(metaDataInput, table))
|
||||
if(!customizer.allowTable(metaDataInput, table))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -119,7 +122,7 @@ public class MetaDataAction
|
||||
String processName = entry.getKey();
|
||||
QProcessMetaData process = entry.getValue();
|
||||
|
||||
if(!filter.allowProcess(metaDataInput, process))
|
||||
if(!customizer.allowProcess(metaDataInput, process))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -144,7 +147,7 @@ public class MetaDataAction
|
||||
String reportName = entry.getKey();
|
||||
QReportMetaData report = entry.getValue();
|
||||
|
||||
if(!filter.allowReport(metaDataInput, report))
|
||||
if(!customizer.allowReport(metaDataInput, report))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -169,7 +172,7 @@ public class MetaDataAction
|
||||
String widgetName = entry.getKey();
|
||||
QWidgetMetaDataInterface widget = entry.getValue();
|
||||
|
||||
if(!filter.allowWidget(metaDataInput, widget))
|
||||
if(!customizer.allowWidget(metaDataInput, widget))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -206,7 +209,7 @@ public class MetaDataAction
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!filter.allowApp(metaDataInput, app))
|
||||
if(!customizer.allowApp(metaDataInput, app))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -292,11 +295,22 @@ public class MetaDataAction
|
||||
metaDataOutput.setBranding(QContext.getQInstance().getBranding());
|
||||
}
|
||||
|
||||
metaDataOutput.setEnvironmentValues(QContext.getQInstance().getEnvironmentValues());
|
||||
metaDataOutput.setEnvironmentValues(Objects.requireNonNullElse(QContext.getQInstance().getEnvironmentValues(), Collections.emptyMap()));
|
||||
|
||||
metaDataOutput.setHelpContents(QContext.getQInstance().getHelpContent());
|
||||
metaDataOutput.setHelpContents(Objects.requireNonNullElse(QContext.getQInstance().getHelpContent(), Collections.emptyMap()));
|
||||
|
||||
// todo post-customization - can do whatever w/ the result if you want?
|
||||
try
|
||||
{
|
||||
customizer.postProcess(metaDataOutput);
|
||||
}
|
||||
catch(QUserFacingException e)
|
||||
{
|
||||
LOG.debug("User-facing exception thrown in meta-data customizer post-processing", e);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.warn("Unexpected error thrown in meta-data customizer post-processing", e);
|
||||
}
|
||||
|
||||
return metaDataOutput;
|
||||
}
|
||||
@ -306,26 +320,36 @@ public class MetaDataAction
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private MetaDataFilterInterface getMetaDataFilter()
|
||||
private MetaDataActionCustomizerInterface getMetaDataActionCustomizer()
|
||||
{
|
||||
return metaDataFilterMemoization.getResult(QContext.getQInstance(), i ->
|
||||
return metaDataActionCustomizerMemoization.getResult(QContext.getQInstance(), i ->
|
||||
{
|
||||
MetaDataFilterInterface filter = null;
|
||||
QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
|
||||
if(metaDataFilterReference != null)
|
||||
MetaDataActionCustomizerInterface actionCustomizer = null;
|
||||
QCodeReference metaDataActionCustomizerReference = QContext.getQInstance().getMetaDataActionCustomizer();
|
||||
if(metaDataActionCustomizerReference != null)
|
||||
{
|
||||
filter = QCodeLoader.getAdHoc(MetaDataFilterInterface.class, metaDataFilterReference);
|
||||
LOG.debug("Using new meta-data filter of type: " + filter.getClass().getSimpleName());
|
||||
actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataActionCustomizerReference);
|
||||
LOG.debug("Using new meta-data actionCustomizer of type: " + actionCustomizer.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
if(filter == null)
|
||||
if(actionCustomizer == null)
|
||||
{
|
||||
filter = new AllowAllMetaDataFilter();
|
||||
LOG.debug("Using new default (allow-all) meta-data filter");
|
||||
QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
|
||||
if(metaDataFilterReference != null)
|
||||
{
|
||||
actionCustomizer = QCodeLoader.getAdHoc(MetaDataActionCustomizerInterface.class, metaDataFilterReference);
|
||||
LOG.debug("Using new meta-data actionCustomizer (via metaDataFilter reference) of type: " + actionCustomizer.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
return (filter);
|
||||
}).orElseThrow(() -> new QRuntimeException("Error getting metaDataFilter"));
|
||||
if(actionCustomizer == null)
|
||||
{
|
||||
actionCustomizer = new DefaultNoopMetaDataActionCustomizer();
|
||||
LOG.debug("Using new default (allow-all) meta-data actionCustomizer");
|
||||
}
|
||||
|
||||
return (actionCustomizer);
|
||||
}).orElseThrow(() -> new QRuntimeException("Error getting MetaDataActionCustomizer"));
|
||||
}
|
||||
|
||||
|
||||
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface for customizations that can be injected by an application into
|
||||
** the MetaDataAction - e.g., loading applicable meta-data for a user into a
|
||||
** frontend.
|
||||
*******************************************************************************/
|
||||
public interface MetaDataActionCustomizerInterface
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowTable(MetaDataInput input, QTableMetaData table);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowProcess(MetaDataInput input, QProcessMetaData process);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowReport(MetaDataInput input, QReportMetaData report);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowApp(MetaDataInput input, QAppMetaData app);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default void postProcess(MetaDataOutput metaDataOutput) throws QException
|
||||
{
|
||||
/////////////////////
|
||||
// noop by default //
|
||||
/////////////////////
|
||||
}
|
||||
|
||||
}
|
@ -22,43 +22,11 @@
|
||||
package com.kingsrook.qqq.backend.core.actions.metadata;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public interface MetaDataFilterInterface
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
public interface MetaDataFilterInterface extends MetaDataActionCustomizerInterface
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowTable(MetaDataInput input, QTableMetaData table);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowProcess(MetaDataInput input, QProcessMetaData process);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowReport(MetaDataInput input, QReportMetaData report);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowApp(MetaDataInput input, QAppMetaData app);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
boolean allowWidget(MetaDataInput input, QWidgetMetaDataInterface widget);
|
||||
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ 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.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;
|
||||
@ -46,6 +48,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaD
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
|
||||
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.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
@ -188,21 +191,40 @@ public class RunBackendStepAction
|
||||
{
|
||||
if(CollectionUtils.nullSafeIsEmpty(runBackendStepInput.getRecords()))
|
||||
{
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(inputMetaData.getRecordListMetaData().getTableName());
|
||||
QTableMetaData table = QContext.getQInstance().getTable(inputMetaData.getRecordListMetaData().getTableName());
|
||||
QueryInput queryInput = new QueryInput();
|
||||
queryInput.setTableName(table.getName());
|
||||
|
||||
// todo - handle this being async (e.g., http)
|
||||
// seems like it just needs to throw, breaking this flow, and to send a response to the frontend, directing it to prompt the user for the needed data
|
||||
// then this step can re-run, hopefully with the needed data.
|
||||
|
||||
QProcessCallback callback = runBackendStepInput.getCallback();
|
||||
if(callback == null)
|
||||
//////////////////////////////////////////////////
|
||||
// look for record ids in the input data values //
|
||||
//////////////////////////////////////////////////
|
||||
String recordIds = (String) runBackendStepInput.getValue("recordIds");
|
||||
if(recordIds == null)
|
||||
{
|
||||
throw (new QUserFacingException("Missing input records.",
|
||||
new QException("Function is missing input records, but no callback was present to request fields from a user")));
|
||||
recordIds = (String) runBackendStepInput.getValue("recordId");
|
||||
}
|
||||
|
||||
queryInput.setFilter(callback.getQueryFilter());
|
||||
///////////////////////////////////////////////////////////
|
||||
// if records were found, add as criteria to query input //
|
||||
///////////////////////////////////////////////////////////
|
||||
if(recordIds != null)
|
||||
{
|
||||
queryInput.setFilter(new QQueryFilter(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, recordIds.split(","))));
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo - handle this being async (e.g., http)
|
||||
// seems like it just needs to throw, breaking this flow, and to send a response to the frontend, directing it to prompt the user for the needed data
|
||||
// then this step can re-run, hopefully with the needed data.
|
||||
QProcessCallback callback = runBackendStepInput.getCallback();
|
||||
if(callback == null)
|
||||
{
|
||||
throw (new QUserFacingException("Missing input records.",
|
||||
new QException("Function is missing input records, but no callback was present to request fields from a user")));
|
||||
}
|
||||
|
||||
queryInput.setFilter(callback.getQueryFilter());
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if process has a max-no of records, set a limit on the process of that number plus 1 //
|
||||
@ -210,7 +232,7 @@ public class RunBackendStepAction
|
||||
//////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(process.getMaxInputRecords() != null)
|
||||
{
|
||||
if(callback.getQueryFilter() == null)
|
||||
if(queryInput.getFilter() == null)
|
||||
{
|
||||
queryInput.setFilter(new QQueryFilter());
|
||||
}
|
||||
|
@ -54,6 +54,14 @@ public interface ExportStreamerInterface
|
||||
// noop in base class
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default void setExportStyleCustomizer(ExportStyleCustomizerInterface exportStyleCustomizer)
|
||||
{
|
||||
// noop in base class
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Called once per sheet, before any rows are available. Meant to write a
|
||||
** header, for example.
|
||||
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.reporting;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface for classes that can be used to customize visual style aspects of
|
||||
** exports/reports.
|
||||
**
|
||||
** Anticipates very different sub-interfaces based on the file type being generated,
|
||||
** and the capabilities of each. e.g., excel (bolds, fonts, cell merging) vs
|
||||
** json (different structure of objects).
|
||||
*******************************************************************************/
|
||||
public interface ExportStyleCustomizerInterface
|
||||
{
|
||||
}
|
@ -163,6 +163,17 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
reportStreamer = reportFormat.newReportStreamer();
|
||||
}
|
||||
|
||||
if(reportInput.getExportStyleCustomizer() != null)
|
||||
{
|
||||
ExportStyleCustomizerInterface styleCustomizer = QCodeLoader.getAdHoc(ExportStyleCustomizerInterface.class, reportInput.getExportStyleCustomizer());
|
||||
reportStreamer.setExportStyleCustomizer(styleCustomizer);
|
||||
}
|
||||
else if(report.getExportStyleCustomizer() != null)
|
||||
{
|
||||
ExportStyleCustomizerInterface styleCustomizer = QCodeLoader.getAdHoc(ExportStyleCustomizerInterface.class, report.getExportStyleCustomizer());
|
||||
reportStreamer.setExportStyleCustomizer(styleCustomizer);
|
||||
}
|
||||
|
||||
reportStreamer.preRun(reportInput.getReportDestination(), views);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -211,7 +222,8 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
if(dataSourceTableView.getViewCustomizer() != null)
|
||||
{
|
||||
Function<QReportView, QReportView> viewCustomizerFunction = QCodeLoader.getFunction(dataSourceTableView.getViewCustomizer());
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<QReportView, QReportView> viewCustomizerFunction = QCodeLoader.getAdHoc(Function.class, dataSourceTableView.getViewCustomizer());
|
||||
if(viewCustomizerFunction instanceof ReportViewCustomizer reportViewCustomizer)
|
||||
{
|
||||
reportViewCustomizer.setReportInput(reportInput);
|
||||
@ -660,7 +672,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// if any fields are 'showPossibleValueLabel', then move display values for them into the record's values map //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
for(QReportField column : tableView.getColumns())
|
||||
for(QReportField column : CollectionUtils.nonNullList(tableView.getColumns()))
|
||||
{
|
||||
if(column.getShowPossibleValueLabel())
|
||||
{
|
||||
|
@ -46,6 +46,7 @@ import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStyleCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ReportUtils;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QReportingException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
||||
@ -77,6 +78,7 @@ import org.apache.poi.xssf.usermodel.XSSFPivotTable;
|
||||
import org.apache.poi.xssf.usermodel.XSSFRow;
|
||||
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -112,9 +114,10 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
public static final String EXCEL_DATE_FORMAT = "yyyy-MM-dd";
|
||||
public static final String EXCEL_DATE_TIME_FORMAT = "yyyy-MM-dd H:mm:ss";
|
||||
|
||||
private PoiExcelStylerInterface poiExcelStylerInterface = getStylerInterface();
|
||||
private ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface;
|
||||
|
||||
private Map<String, String> excelCellFormats;
|
||||
private Map<String, XSSFCellStyle> styles = new HashMap<>();
|
||||
private Map<String, XSSFCellStyle> styles = new HashMap<>();
|
||||
|
||||
private int rowNo = 0;
|
||||
private int sheetIndex = 1;
|
||||
@ -402,6 +405,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
dateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat(EXCEL_DATE_TIME_FORMAT));
|
||||
styles.put("datetime", dateTimeStyle);
|
||||
|
||||
PoiExcelStylerInterface poiExcelStylerInterface = getStylerInterface();
|
||||
styles.put("title", poiExcelStylerInterface.createStyleForTitle(workbook, createHelper));
|
||||
styles.put("header", poiExcelStylerInterface.createStyleForHeader(workbook, createHelper));
|
||||
styles.put("footer", poiExcelStylerInterface.createStyleForFooter(workbook, createHelper));
|
||||
@ -413,6 +417,11 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
XSSFCellStyle footerDateTimeStyle = poiExcelStylerInterface.createStyleForFooter(workbook, createHelper);
|
||||
footerDateTimeStyle.setDataFormat(createHelper.createDataFormat().getFormat(EXCEL_DATE_TIME_FORMAT));
|
||||
styles.put("footer-datetime", footerDateTimeStyle);
|
||||
|
||||
if(styleCustomizerInterface != null)
|
||||
{
|
||||
styleCustomizerInterface.customizeStyles(styles, workbook, createHelper);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -458,7 +467,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
}
|
||||
else
|
||||
{
|
||||
sheetWriter.beginSheet();
|
||||
sheetWriter.beginSheet(view, styleCustomizerInterface);
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// put the title and header rows in the sheet //
|
||||
@ -560,6 +569,16 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static void setStyleForField(QRecord record, String fieldName, String styleName)
|
||||
{
|
||||
record.setDisplayValue(fieldName + ":excelStyle", styleName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -567,12 +586,12 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
{
|
||||
sheetWriter.insertRow(rowNo++);
|
||||
|
||||
int styleIndex = -1;
|
||||
int baseStyleIndex = -1;
|
||||
int dateStyleIndex = styles.get("date").getIndex();
|
||||
int dateTimeStyleIndex = styles.get("datetime").getIndex();
|
||||
if(isFooter)
|
||||
{
|
||||
styleIndex = styles.get("footer").getIndex();
|
||||
baseStyleIndex = styles.get("footer").getIndex();
|
||||
dateStyleIndex = styles.get("footer-date").getIndex();
|
||||
dateTimeStyleIndex = styles.get("footer-datetime").getIndex();
|
||||
}
|
||||
@ -582,6 +601,13 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
{
|
||||
Serializable value = qRecord.getValue(field.getName());
|
||||
|
||||
String overrideStyleName = qRecord.getDisplayValue(field.getName() + ":excelStyle");
|
||||
int styleIndex = baseStyleIndex;
|
||||
if(overrideStyleName != null)
|
||||
{
|
||||
styleIndex = styles.get(overrideStyleName).getIndex();
|
||||
}
|
||||
|
||||
if(value != null)
|
||||
{
|
||||
if(value instanceof String s)
|
||||
@ -706,7 +732,7 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
{
|
||||
if(!ReportType.PIVOT.equals(currentView.getType()))
|
||||
{
|
||||
sheetWriter.endSheet();
|
||||
sheetWriter.endSheet(currentView, styleCustomizerInterface);
|
||||
}
|
||||
|
||||
activeSheetWriter.flush();
|
||||
@ -815,7 +841,29 @@ public class ExcelPoiBasedStreamingExportStreamer implements ExportStreamerInter
|
||||
*******************************************************************************/
|
||||
protected PoiExcelStylerInterface getStylerInterface()
|
||||
{
|
||||
if(styleCustomizerInterface != null)
|
||||
{
|
||||
return styleCustomizerInterface.getExcelStyler();
|
||||
}
|
||||
|
||||
return (new PlainPoiExcelStyler());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void setExportStyleCustomizer(ExportStyleCustomizerInterface exportStyleCustomizer)
|
||||
{
|
||||
if(exportStyleCustomizer instanceof ExcelPoiBasedStreamingStyleCustomizerInterface poiExcelStylerInterface)
|
||||
{
|
||||
this.styleCustomizerInterface = poiExcelStylerInterface;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Supplied export style customizer is not an instance of ExcelPoiStyleCustomizerInterface, so will not be used for an excel export", logPair("exportStyleCustomizerClass", exportStyleCustomizer.getClass().getSimpleName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStyleCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||
import org.apache.poi.ss.usermodel.CreationHelper;
|
||||
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** style customization points for Excel files generated via our streaming POI.
|
||||
*******************************************************************************/
|
||||
public interface ExcelPoiBasedStreamingStyleCustomizerInterface extends ExportStyleCustomizerInterface
|
||||
{
|
||||
/***************************************************************************
|
||||
** slightly legacy way we did excel styles - but get an instance of object
|
||||
** that defaults "default" styles (header, footer, etc).
|
||||
***************************************************************************/
|
||||
default PoiExcelStylerInterface getExcelStyler()
|
||||
{
|
||||
return (new PlainPoiExcelStyler());
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** either change "default" styles put in the styles map, or create new ones
|
||||
** which can then be applied to row/field values (cells) via:
|
||||
** ExcelPoiBasedStreamingExportStreamer.setStyleForField(row, fieldName, styleName);
|
||||
***************************************************************************/
|
||||
default void customizeStyles(Map<String, XSSFCellStyle> styles, XSSFWorkbook workbook, CreationHelper createHelper)
|
||||
{
|
||||
//////////////////
|
||||
// noop default //
|
||||
//////////////////
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** for a given view (sheet), return a list of custom column widths.
|
||||
** any nulls in the list are ignored (so default width is used).
|
||||
***************************************************************************/
|
||||
default List<Integer> getColumnWidthsForView(QReportView view)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** for a given view (sheet), return a list of any ranges which should be
|
||||
** merged, as in "A1:C1" (first three cells in first row).
|
||||
***************************************************************************/
|
||||
default List<String> getMergedRangesForView(QReportView view)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
}
|
@ -25,7 +25,10 @@ package com.kingsrook.qqq.backend.core.actions.reporting.excel.poi;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportView;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import org.apache.poi.ss.util.CellReference;
|
||||
|
||||
|
||||
@ -53,13 +56,33 @@ public class StreamedSheetWriter
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void beginSheet() throws IOException
|
||||
public void beginSheet(QReportView view, ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface) throws IOException
|
||||
{
|
||||
writer.write("""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
|
||||
<sheetData>""");
|
||||
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">""");
|
||||
|
||||
if(styleCustomizerInterface != null && view != null)
|
||||
{
|
||||
List<Integer> columnWidths = styleCustomizerInterface.getColumnWidthsForView(view);
|
||||
if(CollectionUtils.nullSafeHasContents(columnWidths))
|
||||
{
|
||||
writer.write("<cols>");
|
||||
for(int i = 0; i < columnWidths.size(); i++)
|
||||
{
|
||||
Integer width = columnWidths.get(i);
|
||||
if(width != null)
|
||||
{
|
||||
writer.write("""
|
||||
<col min="%d" max="%d" width="%d" customWidth="1"/>
|
||||
""".formatted(i + 1, i + 1, width));
|
||||
}
|
||||
}
|
||||
writer.write("</cols>");
|
||||
}
|
||||
}
|
||||
|
||||
writer.write("<sheetData>");
|
||||
}
|
||||
|
||||
|
||||
@ -67,11 +90,25 @@ public class StreamedSheetWriter
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void endSheet() throws IOException
|
||||
public void endSheet(QReportView view, ExcelPoiBasedStreamingStyleCustomizerInterface styleCustomizerInterface) throws IOException
|
||||
{
|
||||
writer.write("""
|
||||
</sheetData>
|
||||
</worksheet>""");
|
||||
writer.write("</sheetData>");
|
||||
|
||||
if(styleCustomizerInterface != null && view != null)
|
||||
{
|
||||
List<String> mergedRanges = styleCustomizerInterface.getMergedRangesForView(view);
|
||||
if(CollectionUtils.nullSafeHasContents(mergedRanges))
|
||||
{
|
||||
writer.write(String.format("<mergeCells count=\"%d\">", mergedRanges.size()));
|
||||
for(String range : mergedRanges)
|
||||
{
|
||||
writer.write(String.format("<mergeCell ref=\"%s\"/>", range));
|
||||
}
|
||||
writer.write("</mergeCells>");
|
||||
}
|
||||
}
|
||||
|
||||
writer.write("</worksheet>");
|
||||
}
|
||||
|
||||
|
||||
@ -151,7 +188,7 @@ public class StreamedSheetWriter
|
||||
{
|
||||
rs.append(""");
|
||||
}
|
||||
else if (c < 32 && c != '\t' && c != '\n')
|
||||
else if(c < 32 && c != '\t' && c != '\n')
|
||||
{
|
||||
rs.append(' ');
|
||||
}
|
||||
|
@ -53,7 +53,8 @@ public class QJavaExecutor implements QCodeExecutor
|
||||
Serializable output;
|
||||
try
|
||||
{
|
||||
Function<Map<String, Object>, Serializable> function = QCodeLoader.getFunction(codeReference);
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<Map<String, Object>, Serializable> function = QCodeLoader.getAdHoc(Function.class, codeReference);
|
||||
output = function.apply(context);
|
||||
}
|
||||
catch(Exception e)
|
||||
|
@ -401,6 +401,7 @@ public class DeleteAction
|
||||
if(CollectionUtils.nullSafeHasContents(associatedKeys))
|
||||
{
|
||||
DeleteInput nextLevelDeleteInput = new DeleteInput();
|
||||
nextLevelDeleteInput.setFlags(deleteInput.getFlags());
|
||||
nextLevelDeleteInput.setTransaction(deleteInput.getTransaction());
|
||||
nextLevelDeleteInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelDeleteInput.setPrimaryKeys(associatedKeys);
|
||||
|
@ -34,7 +34,6 @@ 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;
|
||||
@ -54,6 +53,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
|
||||
@ -157,7 +157,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
//////////////////////////////////////////////////
|
||||
// insert any associations in the input records //
|
||||
//////////////////////////////////////////////////
|
||||
manageAssociations(table, insertOutput.getRecords(), insertInput.getTransaction());
|
||||
manageAssociations(table, insertOutput.getRecords(), insertInput);
|
||||
|
||||
//////////////////
|
||||
// do the audit //
|
||||
@ -174,9 +174,21 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
.withRecordList(insertOutput.getRecords()));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// finally, run the post-insert customizer, if there is one //
|
||||
//////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////
|
||||
// finally, run the post-insert customizers, if there are any //
|
||||
////////////////////////////////////////////////////////////////
|
||||
runPostInsertCustomizers(insertInput, table, insertOutput);
|
||||
|
||||
return insertOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static void runPostInsertCustomizers(InsertInput insertInput, QTableMetaData table, InsertOutput insertOutput)
|
||||
{
|
||||
Optional<TableCustomizerInterface> postInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_INSERT_RECORD.getRole());
|
||||
if(postInsertCustomizer.isPresent())
|
||||
{
|
||||
@ -193,7 +205,25 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
}
|
||||
}
|
||||
|
||||
return insertOutput;
|
||||
///////////////////////////////////////////////
|
||||
// run all of the instance-level customizers //
|
||||
///////////////////////////////////////////////
|
||||
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.POST_INSERT_RECORD);
|
||||
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
|
||||
insertOutput.setRecords(tableCustomizer.postInsert(insertInput, insertOutput.getRecords()));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
for(QRecord record : insertOutput.getRecords())
|
||||
{
|
||||
record.addWarning(new QWarningMessage("An error occurred after the insert: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -308,6 +338,19 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
insertInput.setRecords(preInsertCustomizer.get().preInsert(insertInput, insertInput.getRecords(), isPreview));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// run all of the instance-level customizers //
|
||||
///////////////////////////////////////////////
|
||||
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.PRE_INSERT_RECORD);
|
||||
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
|
||||
{
|
||||
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
|
||||
if(whenToRun.equals(tableCustomizer.whenToRunPreInsert(insertInput, isPreview)))
|
||||
{
|
||||
insertInput.setRecords(tableCustomizer.preInsert(insertInput, insertInput.getRecords(), isPreview));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -342,7 +385,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void manageAssociations(QTableMetaData table, List<QRecord> insertedRecords, QBackendTransaction transaction) throws QException
|
||||
private void manageAssociations(QTableMetaData table, List<QRecord> insertedRecords, InsertInput insertInput) throws QException
|
||||
{
|
||||
for(Association association : CollectionUtils.nonNullList(table.getAssociations()))
|
||||
{
|
||||
@ -375,7 +418,8 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
|
||||
if(CollectionUtils.nullSafeHasContents(nextLevelInserts))
|
||||
{
|
||||
InsertInput nextLevelInsertInput = new InsertInput();
|
||||
nextLevelInsertInput.setTransaction(transaction);
|
||||
nextLevelInsertInput.withFlags(insertInput.getFlags());
|
||||
nextLevelInsertInput.setTransaction(insertInput.getTransaction());
|
||||
nextLevelInsertInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelInsertInput.setRecords(nextLevelInserts);
|
||||
InsertOutput nextLevelInsertOutput = new InsertAction().execute(nextLevelInsertInput);
|
||||
|
@ -126,6 +126,7 @@ public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, Replace
|
||||
InsertInput insertInput = new InsertInput();
|
||||
insertInput.setTableName(table.getName());
|
||||
insertInput.setRecords(insertList);
|
||||
insertInput.withFlags(input.getFlags());
|
||||
insertInput.setTransaction(transaction);
|
||||
insertInput.setOmitDmlAudit(input.getOmitDmlAudit());
|
||||
InsertOutput insertOutput = new InsertAction().execute(insertInput);
|
||||
@ -135,6 +136,7 @@ public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, Replace
|
||||
UpdateInput updateInput = new UpdateInput();
|
||||
updateInput.setTableName(table.getName());
|
||||
updateInput.setRecords(updateList);
|
||||
updateInput.withFlags(input.getFlags());
|
||||
updateInput.setTransaction(transaction);
|
||||
updateInput.setOmitDmlAudit(input.getOmitDmlAudit());
|
||||
UpdateOutput updateOutput = new UpdateAction().execute(updateInput);
|
||||
@ -151,6 +153,7 @@ public class ReplaceAction extends AbstractQActionFunction<ReplaceInput, Replace
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setTableName(table.getName());
|
||||
deleteInput.setQueryFilter(deleteFilter);
|
||||
deleteInput.withFlags(input.getFlags());
|
||||
deleteInput.setTransaction(transaction);
|
||||
deleteInput.setOmitDmlAudit(input.getOmitDmlAudit());
|
||||
DeleteOutput deleteOutput = new DeleteAction().execute(deleteInput);
|
||||
|
@ -57,6 +57,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateOutput;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
@ -199,6 +200,18 @@ public class UpdateAction
|
||||
//////////////////////////////////////////////////////////////
|
||||
// finally, run the post-update customizer, if there is one //
|
||||
//////////////////////////////////////////////////////////////
|
||||
runPostUpdateCustomizers(updateInput, table, updateOutput, oldRecordList);
|
||||
|
||||
return updateOutput;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static void runPostUpdateCustomizers(UpdateInput updateInput, QTableMetaData table, UpdateOutput updateOutput, Optional<List<QRecord>> oldRecordList)
|
||||
{
|
||||
Optional<TableCustomizerInterface> postUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.POST_UPDATE_RECORD.getRole());
|
||||
if(postUpdateCustomizer.isPresent())
|
||||
{
|
||||
@ -215,7 +228,49 @@ public class UpdateAction
|
||||
}
|
||||
}
|
||||
|
||||
return updateOutput;
|
||||
///////////////////////////////////////////////
|
||||
// run all of the instance-level customizers //
|
||||
///////////////////////////////////////////////
|
||||
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.POST_UPDATE_RECORD);
|
||||
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
|
||||
{
|
||||
try
|
||||
{
|
||||
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
|
||||
updateOutput.setRecords(tableCustomizer.postUpdate(updateInput, updateOutput.getRecords(), oldRecordList));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
for(QRecord record : updateOutput.getRecords())
|
||||
{
|
||||
record.addWarning(new QWarningMessage("An error occurred after the update: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static void runPreUpdateCustomizers(UpdateInput updateInput, QTableMetaData table, Optional<List<QRecord>> oldRecordList, boolean isPreview) throws QException
|
||||
{
|
||||
Optional<TableCustomizerInterface> preUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
|
||||
if(preUpdateCustomizer.isPresent())
|
||||
{
|
||||
updateInput.setRecords(preUpdateCustomizer.get().preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////
|
||||
// run all of the instance-level customizers //
|
||||
///////////////////////////////////////////////
|
||||
List<QCodeReference> tableCustomizerCodes = QContext.getQInstance().getTableCustomizers(TableCustomizers.PRE_UPDATE_RECORD);
|
||||
for(QCodeReference tableCustomizerCode : tableCustomizerCodes)
|
||||
{
|
||||
TableCustomizerInterface tableCustomizer = QCodeLoader.getAdHoc(TableCustomizerInterface.class, tableCustomizerCode);
|
||||
updateInput.setRecords(tableCustomizer.preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -278,11 +333,7 @@ public class UpdateAction
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// after all validations, run the pre-update customizer, if there is one //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
Optional<TableCustomizerInterface> preUpdateCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_UPDATE_RECORD.getRole());
|
||||
if(preUpdateCustomizer.isPresent())
|
||||
{
|
||||
updateInput.setRecords(preUpdateCustomizer.get().preUpdate(updateInput, updateInput.getRecords(), isPreview, oldRecordList));
|
||||
}
|
||||
runPreUpdateCustomizers(updateInput, table, oldRecordList, isPreview);
|
||||
}
|
||||
|
||||
|
||||
@ -405,7 +456,7 @@ public class UpdateAction
|
||||
QFieldType fieldType = table.getField(lock.getFieldName()).getType();
|
||||
Serializable lockValue = ValueUtils.getValueAsFieldType(fieldType, oldRecord.getValue(lock.getFieldName()));
|
||||
|
||||
List<QErrorMessage> errors = ValidateRecordSecurityLockHelper.validateRecordSecurityValue(table, lock, lockValue, fieldType, ValidateRecordSecurityLockHelper.Action.UPDATE, Collections.emptyMap());
|
||||
List<QErrorMessage> errors = ValidateRecordSecurityLockHelper.validateRecordSecurityValue(table, lock, lockValue, fieldType, ValidateRecordSecurityLockHelper.Action.UPDATE, Collections.emptyMap(), QContext.getQSession());
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
errors.forEach(e -> record.addError(e));
|
||||
@ -554,6 +605,7 @@ public class UpdateAction
|
||||
{
|
||||
LOG.debug("Deleting associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", queryOutput.getRecords().size()));
|
||||
DeleteInput deleteInput = new DeleteInput();
|
||||
deleteInput.setFlags(updateInput.getFlags());
|
||||
deleteInput.setTransaction(updateInput.getTransaction());
|
||||
deleteInput.setTableName(association.getAssociatedTableName());
|
||||
deleteInput.setPrimaryKeys(queryOutput.getRecords().stream().map(r -> r.getValue(associatedTable.getPrimaryKeyField())).collect(Collectors.toList()));
|
||||
@ -566,6 +618,7 @@ public class UpdateAction
|
||||
LOG.debug("Updating associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", nextLevelUpdates.size()));
|
||||
UpdateInput nextLevelUpdateInput = new UpdateInput();
|
||||
nextLevelUpdateInput.setTransaction(updateInput.getTransaction());
|
||||
nextLevelUpdateInput.setFlags(updateInput.getFlags());
|
||||
nextLevelUpdateInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelUpdateInput.setRecords(nextLevelUpdates);
|
||||
UpdateOutput nextLevelUpdateOutput = new UpdateAction().execute(nextLevelUpdateInput);
|
||||
@ -576,6 +629,7 @@ public class UpdateAction
|
||||
LOG.debug("Inserting associatedRecords", logPair("associatedTable", associatedTable.getName()), logPair("noOfRecords", nextLevelUpdates.size()));
|
||||
InsertInput nextLevelInsertInput = new InsertInput();
|
||||
nextLevelInsertInput.setTransaction(updateInput.getTransaction());
|
||||
nextLevelInsertInput.setFlags(updateInput.getFlags());
|
||||
nextLevelInsertInput.setTableName(association.getAssociatedTableName());
|
||||
nextLevelInsertInput.setRecords(nextLevelInserts);
|
||||
InsertOutput nextLevelInsertOutput = new InsertAction().execute(nextLevelInsertInput);
|
||||
|
@ -50,6 +50,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.RecordSecurityLockFilters;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.session.QSession;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.PermissionDeniedMessage;
|
||||
import com.kingsrook.qqq.backend.core.model.statusmessages.QErrorMessage;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
@ -102,7 +103,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
// actually check lock values //
|
||||
////////////////////////////////
|
||||
Map<Serializable, RecordWithErrors> errorRecords = new HashMap<>();
|
||||
evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>(), madeUpPrimaryKeys, transaction);
|
||||
evaluateRecordLocks(table, records, action, locksToCheck, errorRecords, new ArrayList<>(), madeUpPrimaryKeys, transaction, QContext.getQSession());
|
||||
|
||||
/////////////////////////////////
|
||||
// propagate errors to records //
|
||||
@ -124,6 +125,29 @@ public class ValidateRecordSecurityLockHelper
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** return boolean if given session can read given record
|
||||
***************************************************************************/
|
||||
public static boolean allowedToReadRecord(QTableMetaData table, QRecord record, QSession qSession, QBackendTransaction transaction) throws QException
|
||||
{
|
||||
MultiRecordSecurityLock locksToCheck = getRecordSecurityLocks(table, Action.SELECT);
|
||||
if(locksToCheck == null || CollectionUtils.nullSafeIsEmpty(locksToCheck.getLocks()))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
|
||||
Map<Serializable, RecordWithErrors> errorRecords = new HashMap<>();
|
||||
evaluateRecordLocks(table, List.of(record), Action.SELECT, locksToCheck, errorRecords, new ArrayList<>(), Collections.emptyMap(), transaction, qSession);
|
||||
if(errorRecords.containsKey(record.getValue(table.getPrimaryKeyField())))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** For a list of `records` from a `table`, and a given `action`, evaluate a
|
||||
** `recordSecurityLock` (which may be a multi-lock) - populating the input map
|
||||
@ -142,7 +166,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
** BUT - WRITE locks - in their case, we read the record no matter what, and in
|
||||
** here we need to verify we have a key that allows us to WRITE the record.
|
||||
*******************************************************************************/
|
||||
private static void evaluateRecordLocks(QTableMetaData table, List<QRecord> records, Action action, RecordSecurityLock recordSecurityLock, Map<Serializable, RecordWithErrors> errorRecords, List<Integer> treePosition, Map<Serializable, QRecord> madeUpPrimaryKeys, QBackendTransaction transaction) throws QException
|
||||
private static void evaluateRecordLocks(QTableMetaData table, List<QRecord> records, Action action, RecordSecurityLock recordSecurityLock, Map<Serializable, RecordWithErrors> errorRecords, List<Integer> treePosition, Map<Serializable, QRecord> madeUpPrimaryKeys, QBackendTransaction transaction, QSession qSession) throws QException
|
||||
{
|
||||
if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock)
|
||||
{
|
||||
@ -153,7 +177,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
for(RecordSecurityLock childLock : CollectionUtils.nonNullList(multiRecordSecurityLock.getLocks()))
|
||||
{
|
||||
treePosition.add(i);
|
||||
evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition, madeUpPrimaryKeys, transaction);
|
||||
evaluateRecordLocks(table, records, action, childLock, errorRecords, treePosition, madeUpPrimaryKeys, transaction, qSession);
|
||||
treePosition.remove(treePosition.size() - 1);
|
||||
i++;
|
||||
}
|
||||
@ -165,7 +189,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
// if this lock has an all-access key, and the user has that key, then there can't be any errors here, so return early //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
QSecurityKeyType securityKeyType = QContext.getQInstance().getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
|
||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && QContext.getQSession().hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
|
||||
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()) && qSession.hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -193,7 +217,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
}
|
||||
|
||||
Serializable recordSecurityValue = record.getValue(field.getName());
|
||||
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys);
|
||||
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys, qSession);
|
||||
if(CollectionUtils.nullSafeHasContents(recordErrors))
|
||||
{
|
||||
errorRecords.computeIfAbsent(record.getValue(primaryKeyField), (k) -> new RecordWithErrors(record)).addAll(recordErrors, treePosition);
|
||||
@ -339,7 +363,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
|
||||
for(QRecord inputRecord : inputRecords)
|
||||
{
|
||||
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys);
|
||||
List<QErrorMessage> recordErrors = validateRecordSecurityValue(table, recordSecurityLock, recordSecurityValue, field.getType(), action, madeUpPrimaryKeys, qSession);
|
||||
if(CollectionUtils.nullSafeHasContents(recordErrors))
|
||||
{
|
||||
errorRecords.computeIfAbsent(inputRecord.getValue(primaryKeyField), (k) -> new RecordWithErrors(inputRecord)).addAll(recordErrors, treePosition);
|
||||
@ -446,7 +470,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<QErrorMessage> validateRecordSecurityValue(QTableMetaData table, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action, Map<Serializable, QRecord> madeUpPrimaryKeys)
|
||||
public static List<QErrorMessage> validateRecordSecurityValue(QTableMetaData table, RecordSecurityLock recordSecurityLock, Serializable recordSecurityValue, QFieldType fieldType, Action action, Map<Serializable, QRecord> madeUpPrimaryKeys, QSession qSession)
|
||||
{
|
||||
if(recordSecurityValue == null || (madeUpPrimaryKeys != null && madeUpPrimaryKeys.containsKey(recordSecurityValue)))
|
||||
{
|
||||
@ -461,7 +485,7 @@ public class ValidateRecordSecurityLockHelper
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!QContext.getQSession().hasSecurityKeyValue(recordSecurityLock.getSecurityKeyType(), recordSecurityValue, fieldType))
|
||||
if(!qSession.hasSecurityKeyValue(recordSecurityLock.getSecurityKeyType(), recordSecurityValue, fieldType))
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(recordSecurityLock.getJoinNameChain()))
|
||||
{
|
||||
|
@ -26,22 +26,32 @@ import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.actions.AbstractQActionFunction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.templates.ConvertHtmlToPdfInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.templates.ConvertHtmlToPdfOutput;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.openhtmltopdf.css.constants.IdentValue;
|
||||
import com.openhtmltopdf.pdfboxout.PdfBoxFontResolver;
|
||||
import com.openhtmltopdf.pdfboxout.PdfBoxRenderer;
|
||||
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.helper.W3CDom;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.xhtmlrenderer.layout.SharedContext;
|
||||
import org.xhtmlrenderer.pdf.ITextRenderer;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Action to convert a string of HTML to a PDF!
|
||||
**
|
||||
** Much credit to https://www.baeldung.com/java-html-to-pdf
|
||||
*******************************************************************************/
|
||||
**
|
||||
** Updated in March 2025 to go from flying-saucer-pdf-openpdf lib to openhtmltopdf,
|
||||
** mostly to get support for max-height on images...
|
||||
********************************************************************************/
|
||||
public class ConvertHtmlToPdfAction extends AbstractQActionFunction<ConvertHtmlToPdfInput, ConvertHtmlToPdfOutput>
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(ConvertHtmlToPdfAction.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
@ -58,35 +68,37 @@ public class ConvertHtmlToPdfAction extends AbstractQActionFunction<ConvertHtmlT
|
||||
//////////////////////////////////////////////////////////////////
|
||||
Document document = Jsoup.parse(input.getHtml());
|
||||
document.outputSettings().syntax(Document.OutputSettings.Syntax.xml);
|
||||
org.w3c.dom.Document w3cDoc = new W3CDom().fromJsoup(document);
|
||||
|
||||
//////////////////////////////
|
||||
// convert the XHTML to PDF //
|
||||
//////////////////////////////
|
||||
ITextRenderer renderer = new ITextRenderer();
|
||||
SharedContext sharedContext = renderer.getSharedContext();
|
||||
sharedContext.setPrint(true);
|
||||
sharedContext.setInteractive(false);
|
||||
PdfRendererBuilder builder = new PdfRendererBuilder();
|
||||
builder.toStream(input.getOutputStream());
|
||||
builder.useFastMode();
|
||||
builder.withW3cDocument(w3cDoc, input.getBasePath() == null ? "./" : input.getBasePath().toUri().toString());
|
||||
|
||||
if(input.getBasePath() != null)
|
||||
try(PdfBoxRenderer pdfBoxRenderer = builder.buildPdfRenderer())
|
||||
{
|
||||
String baseUrl = input.getBasePath().toUri().toURL().toString();
|
||||
renderer.setDocumentFromString(document.html(), baseUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
renderer.setDocumentFromString(document.html());
|
||||
}
|
||||
pdfBoxRenderer.layout();
|
||||
pdfBoxRenderer.getSharedContext().setPrint(true);
|
||||
pdfBoxRenderer.getSharedContext().setInteractive(false);
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// register any custom fonts the input supplied //
|
||||
//////////////////////////////////////////////////
|
||||
for(Map.Entry<String, Path> entry : CollectionUtils.nonNullMap(input.getCustomFonts()).entrySet())
|
||||
{
|
||||
renderer.getFontResolver().addFont(entry.getValue().toAbsolutePath().toString(), entry.getKey(), "UTF-8", true, null);
|
||||
}
|
||||
for(Map.Entry<String, Path> entry : CollectionUtils.nonNullMap(input.getCustomFonts()).entrySet())
|
||||
{
|
||||
LOG.warn("Note: Custom fonts appear to not be working in this class at this time...");
|
||||
pdfBoxRenderer.getFontResolver().addFont(
|
||||
entry.getValue().toAbsolutePath().toFile(), // Path to the TrueType font file
|
||||
entry.getKey(), // Font family name to use in CSS
|
||||
400, // Font weight (e.g., 400 for normal, 700 for bold)
|
||||
IdentValue.NORMAL, // Font style (e.g., NORMAL, ITALIC)
|
||||
true, // Whether to subset the font
|
||||
PdfBoxFontResolver.FontGroup.MAIN // ??
|
||||
);
|
||||
}
|
||||
|
||||
renderer.layout();
|
||||
renderer.createPDF(input.getOutputStream());
|
||||
pdfBoxRenderer.createPDF();
|
||||
}
|
||||
|
||||
return (output);
|
||||
}
|
||||
|
@ -0,0 +1,91 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.actions.values;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValue;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Basic implementation of a possible value provider, for where there's a limited
|
||||
** set of possible source objects - so you just have to define how to make one
|
||||
** PV from a source object, how to list all of the source objects, and how to
|
||||
** look up a PV from an id.
|
||||
*******************************************************************************/
|
||||
public abstract class BasicCustomPossibleValueProvider<S, ID extends Serializable> implements QCustomPossibleValueProvider<ID>
|
||||
{
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
protected abstract QPossibleValue<ID> makePossibleValue(S sourceObject);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
protected abstract S getSourceObject(Serializable id) throws QException;
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
protected abstract List<S> getAllSourceObjects() throws QException;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QPossibleValue<ID> getPossibleValue(Serializable idValue) throws QException
|
||||
{
|
||||
S sourceObject = getSourceObject(idValue);
|
||||
if(sourceObject == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return makePossibleValue(sourceObject);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public List<QPossibleValue<ID>> search(SearchPossibleValueSourceInput input) throws QException
|
||||
{
|
||||
List<QPossibleValue<ID>> allPossibleValues = new ArrayList<>();
|
||||
List<S> allSourceObjects = getAllSourceObjects();
|
||||
for(S sourceObject : allSourceObjects)
|
||||
{
|
||||
allPossibleValues.add(makePossibleValue(sourceObject));
|
||||
}
|
||||
|
||||
return completeCustomPVSSearch(input, allPossibleValues);
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@ public interface QCustomPossibleValueProvider<T extends Serializable>
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
QPossibleValue<T> getPossibleValue(Serializable idValue);
|
||||
QPossibleValue<T> getPossibleValue(Serializable idValue) throws QException;
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -341,7 +341,7 @@ public class QPossibleValueTranslator
|
||||
|
||||
try
|
||||
{
|
||||
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
||||
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
|
||||
return (formatPossibleValue(possibleValueSource, customPossibleValueProvider.getPossibleValue(value)));
|
||||
}
|
||||
catch(Exception e)
|
||||
|
@ -424,7 +424,7 @@ public class SearchPossibleValueSourceAction
|
||||
{
|
||||
try
|
||||
{
|
||||
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
||||
QCustomPossibleValueProvider customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
|
||||
List<QPossibleValue<?>> possibleValues = customPossibleValueProvider.search(input);
|
||||
|
||||
SearchPossibleValueSourceOutput output = new SearchPossibleValueSourceOutput();
|
||||
|
@ -29,7 +29,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
/*******************************************************************************
|
||||
** Version of AbstractQQQApplication that assumes all meta-data is produced
|
||||
** by MetaDataProducers in a single package.
|
||||
** by MetaDataProducers in (or under) a single package.
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractMetaDataProducerBasedQQQApplication extends AbstractQQQApplication
|
||||
{
|
||||
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.MetaDataLoaderHelper;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Version of AbstractQQQApplication that assumes all meta-data is defined in
|
||||
** config files (yaml, json, etc) under a given directory path.
|
||||
*******************************************************************************/
|
||||
public class ConfigFilesBasedQQQApplication extends AbstractQQQApplication
|
||||
{
|
||||
private final String path;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ConfigFilesBasedQQQApplication(String path)
|
||||
{
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QInstance defineQInstance() throws QException
|
||||
{
|
||||
QInstance qInstance = new QInstance();
|
||||
MetaDataLoaderHelper.processAllMetaDataFilesInDirectory(qInstance, path);
|
||||
return (qInstance);
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Version of AbstractQQQApplication that assumes all meta-data is produced
|
||||
** by MetaDataProducers in (or under) a single package (where you can pass that
|
||||
** package into the constructor, vs. the abstract base class, where you extend
|
||||
** it and override the getMetaDataPackageName method.
|
||||
*******************************************************************************/
|
||||
public class MetaDataProducerBasedQQQApplication extends AbstractMetaDataProducerBasedQQQApplication
|
||||
{
|
||||
private final String metaDataPackageName;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MetaDataProducerBasedQQQApplication(String metaDataPackageName)
|
||||
{
|
||||
this.metaDataPackageName = metaDataPackageName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public MetaDataProducerBasedQQQApplication(Class<?> aClassInMetaDataPackage)
|
||||
{
|
||||
this(aClassInMetaDataPackage.getPackageName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String getMetaDataPackageName()
|
||||
{
|
||||
return (this.metaDataPackageName);
|
||||
}
|
||||
}
|
@ -47,6 +47,7 @@ import com.kingsrook.qqq.backend.core.instances.enrichment.plugins.QInstanceEnri
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
|
||||
@ -210,6 +211,11 @@ public class QInstanceEnricher
|
||||
***************************************************************************/
|
||||
private void enrichInstance()
|
||||
{
|
||||
for(QSupplementalInstanceMetaData supplementalInstanceMetaData : qInstance.getSupplementalMetaData().values())
|
||||
{
|
||||
supplementalInstanceMetaData.enrich(qInstance);
|
||||
}
|
||||
|
||||
runPlugins(QInstance.class, qInstance, qInstance);
|
||||
}
|
||||
|
||||
@ -330,7 +336,21 @@ public class QInstanceEnricher
|
||||
|
||||
if(table.getFields() != null)
|
||||
{
|
||||
table.getFields().values().forEach(this::enrichField);
|
||||
for(Map.Entry<String, QFieldMetaData> entry : table.getFields().entrySet())
|
||||
{
|
||||
String name = entry.getKey();
|
||||
QFieldMetaData field = entry.getValue();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// in case the field wasn't given a name, use its key from the fields map //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
if(!StringUtils.hasContent(field.getName()))
|
||||
{
|
||||
field.setName(name);
|
||||
}
|
||||
|
||||
enrichField(field);
|
||||
}
|
||||
|
||||
for(QSupplementalTableMetaData supplementalTableMetaData : CollectionUtils.nonNullMap(table.getSupplementalMetaData()).values())
|
||||
{
|
||||
@ -1390,7 +1410,7 @@ public class QInstanceEnricher
|
||||
if(possibleValueSource.getIdType() == null)
|
||||
{
|
||||
QTableMetaData table = qInstance.getTable(possibleValueSource.getTableName());
|
||||
if(table != null)
|
||||
if(table != null && table.getFields() != null)
|
||||
{
|
||||
String primaryKeyField = table.getPrimaryKeyField();
|
||||
QFieldMetaData primaryKeyFieldMetaData = table.getFields().get(primaryKeyField);
|
||||
@ -1425,7 +1445,7 @@ public class QInstanceEnricher
|
||||
{
|
||||
try
|
||||
{
|
||||
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
|
||||
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getAdHoc(QCustomPossibleValueProvider.class, possibleValueSource.getCustomCodeReference());
|
||||
|
||||
Method getPossibleValueMethod = customPossibleValueProvider.getClass().getDeclaredMethod("getPossibleValue", Serializable.class);
|
||||
Type returnType = getPossibleValueMethod.getGenericReturnType();
|
||||
@ -1463,7 +1483,18 @@ public class QInstanceEnricher
|
||||
if(enrichMethod.isPresent())
|
||||
{
|
||||
Class<?> parameterType = enrichMethod.get().getParameterTypes()[0];
|
||||
enricherPlugins.add(parameterType, plugin);
|
||||
|
||||
Set<String> existingPluginIdentifiers = enricherPlugins.getOrDefault(parameterType, Collections.emptyList())
|
||||
.stream().map(p -> p.getPluginIdentifier())
|
||||
.collect(Collectors.toSet());
|
||||
if(existingPluginIdentifiers.contains(plugin.getPluginIdentifier()))
|
||||
{
|
||||
LOG.debug("Enricher plugin is already registered - not re-adding it", logPair("pluginIdentifer", plugin.getPluginIdentifier()));
|
||||
}
|
||||
else
|
||||
{
|
||||
enricherPlugins.add(parameterType, plugin);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -42,9 +42,11 @@ import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataActionCustomizerInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataFilterInterface;
|
||||
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.customizers.ReportCustomRecordSourceInterface;
|
||||
@ -63,6 +65,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QSupplementalInstanceMetaDa
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.automation.QAutomationProviderMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceLambda;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.ParentWidgetMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
|
||||
@ -245,6 +248,22 @@ public class QInstanceValidator
|
||||
{
|
||||
validateSimpleCodeReference("Instance metaDataFilter ", qInstance.getMetaDataFilter(), MetaDataFilterInterface.class);
|
||||
}
|
||||
|
||||
if(qInstance.getMetaDataActionCustomizer() != null)
|
||||
{
|
||||
validateSimpleCodeReference("Instance metaDataActionCustomizer ", qInstance.getMetaDataActionCustomizer(), MetaDataActionCustomizerInterface.class);
|
||||
}
|
||||
|
||||
if(qInstance.getTableCustomizers() != null)
|
||||
{
|
||||
for(Map.Entry<String, List<QCodeReference>> entry : qInstance.getTableCustomizers().entrySet())
|
||||
{
|
||||
for(QCodeReference codeReference : CollectionUtils.nonNullList(entry.getValue()))
|
||||
{
|
||||
validateSimpleCodeReference("Instance tableCustomizer of type " + entry.getKey() + ": ", codeReference, TableCustomizerInterface.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -276,7 +295,18 @@ public class QInstanceValidator
|
||||
if(validateMethod.isPresent())
|
||||
{
|
||||
Class<?> parameterType = validateMethod.get().getParameterTypes()[0];
|
||||
validatorPlugins.add(parameterType, plugin);
|
||||
|
||||
Set<String> existingPluginIdentifiers = validatorPlugins.getOrDefault(parameterType, Collections.emptyList())
|
||||
.stream().map(p -> p.getPluginIdentifier())
|
||||
.collect(Collectors.toSet());
|
||||
if(existingPluginIdentifiers.contains(plugin.getPluginIdentifier()))
|
||||
{
|
||||
LOG.debug("Validator plugin is already registered - not re-adding it", logPair("pluginIdentifer", plugin.getPluginIdentifier()));
|
||||
}
|
||||
else
|
||||
{
|
||||
validatorPlugins.add(parameterType, plugin);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -296,6 +326,17 @@ public class QInstanceValidator
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for validatorPlugins
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static ListingHash<Class<?>, QInstanceValidatorPluginInterface<?>> getValidatorPlugins()
|
||||
{
|
||||
return validatorPlugins;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -645,6 +686,8 @@ public class QInstanceValidator
|
||||
validateSimpleCodeReference("Instance Authentication meta data customizer ", authentication.getCustomizer(), QAuthenticationModuleCustomizerInterface.class);
|
||||
}
|
||||
|
||||
authentication.validate(qInstance, this);
|
||||
|
||||
runPlugins(QAuthenticationMetaData.class, authentication, qInstance);
|
||||
}
|
||||
}
|
||||
@ -1400,7 +1443,7 @@ public class QInstanceValidator
|
||||
//////////////////////////////////////////////////
|
||||
// make sure the customizer can be instantiated //
|
||||
//////////////////////////////////////////////////
|
||||
Object customizerInstance = getInstanceOfCodeReference(prefix, customizerClass);
|
||||
Object customizerInstance = getInstanceOfCodeReference(prefix, customizerClass, codeReference);
|
||||
|
||||
TableCustomizers tableCustomizer = TableCustomizers.forRole(roleName);
|
||||
if(tableCustomizer == null)
|
||||
@ -1461,8 +1504,13 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private Object getInstanceOfCodeReference(String prefix, Class<?> clazz)
|
||||
private Object getInstanceOfCodeReference(String prefix, Class<?> clazz, QCodeReference codeReference)
|
||||
{
|
||||
if(codeReference instanceof QCodeReferenceLambda<?> lambdaCodeReference)
|
||||
{
|
||||
return (lambdaCodeReference.getLambda());
|
||||
}
|
||||
|
||||
Object instance = null;
|
||||
try
|
||||
{
|
||||
@ -1641,21 +1689,26 @@ public class QInstanceValidator
|
||||
Set<String> usedStepNames = new HashSet<>();
|
||||
if(assertCondition(CollectionUtils.nullSafeHasContents(process.getStepList()), "At least 1 step must be defined in process " + processName + "."))
|
||||
{
|
||||
int index = 0;
|
||||
int index = -1;
|
||||
for(QStepMetaData step : process.getStepList())
|
||||
{
|
||||
index++;
|
||||
if(assertCondition(StringUtils.hasContent(step.getName()), "Missing name for a step at index " + index + " in process " + processName))
|
||||
{
|
||||
assertCondition(!usedStepNames.contains(step.getName()), "Duplicate step name [" + step.getName() + "] in process " + processName);
|
||||
usedStepNames.add(step.getName());
|
||||
}
|
||||
index++;
|
||||
|
||||
////////////////////////////////////////////
|
||||
// validate instantiation of step classes //
|
||||
////////////////////////////////////////////
|
||||
if(step instanceof QBackendStepMetaData backendStepMetaData)
|
||||
{
|
||||
if(assertCondition(backendStepMetaData.getCode() != null, "Missing code for a backend step at index " + index + " in process " + processName))
|
||||
{
|
||||
validateSimpleCodeReference("Process " + processName + ", backend step at index " + index + ", code reference: ", backendStepMetaData.getCode(), BackendStep.class);
|
||||
}
|
||||
|
||||
if(backendStepMetaData.getInputMetaData() != null && CollectionUtils.nullSafeHasContents(backendStepMetaData.getInputMetaData().getFieldList()))
|
||||
{
|
||||
for(QFieldMetaData fieldMetaData : backendStepMetaData.getInputMetaData().getFieldList())
|
||||
@ -2219,8 +2272,7 @@ public class QInstanceValidator
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SafeVarargs
|
||||
private void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class<?>... anyOfExpectedClasses)
|
||||
public void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class<?>... anyOfExpectedClasses)
|
||||
{
|
||||
if(!preAssertionsForCodeReference(codeReference, prefix))
|
||||
{
|
||||
@ -2241,7 +2293,7 @@ public class QInstanceValidator
|
||||
//////////////////////////////////////////////////
|
||||
// make sure the customizer can be instantiated //
|
||||
//////////////////////////////////////////////////
|
||||
Object classInstance = getInstanceOfCodeReference(prefix, clazz);
|
||||
Object classInstance = getInstanceOfCodeReference(prefix, clazz, codeReference);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// make sure the customizer instance can be cast to the expected type //
|
||||
@ -2264,6 +2316,11 @@ public class QInstanceValidator
|
||||
Class<?> clazz = null;
|
||||
try
|
||||
{
|
||||
if(codeReference instanceof QCodeReferenceLambda<?> lambdaCodeReference)
|
||||
{
|
||||
return (lambdaCodeReference.getLambda().getClass());
|
||||
}
|
||||
|
||||
clazz = Class.forName(codeReference.getName());
|
||||
}
|
||||
catch(ClassNotFoundException e)
|
||||
|
@ -72,6 +72,18 @@ public class SecretsManagerUtils
|
||||
** and write them to a .env file (backing up any pre-existing .env files first).
|
||||
*******************************************************************************/
|
||||
public static void writeEnvFromSecretsWithNamePrefix(String prefix) throws IOException
|
||||
{
|
||||
writeEnvFromSecretsWithNamePrefix(prefix, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** IF secret manager ENV vars are set,
|
||||
** THEN lookup all secrets starting with the given prefix,
|
||||
** and write them to a .env file (backing up any pre-existing .env files first).
|
||||
*******************************************************************************/
|
||||
public static void writeEnvFromSecretsWithNamePrefix(String prefix, boolean quoteValues) throws IOException
|
||||
{
|
||||
Optional<AWSSecretsManager> optionalSecretsManagerClient = getSecretsManagerClient();
|
||||
if(optionalSecretsManagerClient.isPresent())
|
||||
@ -91,7 +103,9 @@ public class SecretsManagerUtils
|
||||
Optional<String> secretValue = getSecret(prefix, nameWithoutPrefix);
|
||||
if(secretValue.isPresent())
|
||||
{
|
||||
String envLine = nameWithoutPrefix + "=" + secretValue.get();
|
||||
String envLine = quoteValues
|
||||
? nameWithoutPrefix + "=\"" + secretValue.get() + "\""
|
||||
: nameWithoutPrefix + "=" + secretValue.get();
|
||||
fullEnv.append(envLine).append('\n');
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.assessment;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** marker for an object which can be processed by the QInstanceAssessor.
|
||||
*******************************************************************************/
|
||||
public interface Assessable
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
void assess(QInstanceAssessor qInstanceAssessor, QInstance qInstance);
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.assessment;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** POC of a class that is meant to review meta-data for accuracy vs. real backends.
|
||||
*******************************************************************************/
|
||||
public class QInstanceAssessor
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QInstanceAssessor.class);
|
||||
|
||||
private final QInstance qInstance;
|
||||
|
||||
private List<String> errors = new ArrayList<>();
|
||||
private List<String> warnings = new ArrayList<>();
|
||||
private List<String> suggestions = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QInstanceAssessor(QInstance qInstance)
|
||||
{
|
||||
this.qInstance = qInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void assess()
|
||||
{
|
||||
for(QBackendMetaData backend : qInstance.getBackends().values())
|
||||
{
|
||||
if(backend instanceof Assessable assessable)
|
||||
{
|
||||
assessable.assess(this, qInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
|
||||
public String getSummary()
|
||||
{
|
||||
StringBuilder rs = new StringBuilder();
|
||||
|
||||
///////////////////////////
|
||||
// print header & errors //
|
||||
///////////////////////////
|
||||
if(CollectionUtils.nullSafeIsEmpty(errors))
|
||||
{
|
||||
rs.append("Assessment passed with no errors! \uD83D\uDE0E\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
rs.append("Assessment found the following ").append(StringUtils.plural(errors, "error", "errors")).append(": \uD83D\uDE32\n");
|
||||
|
||||
for(String error : errors)
|
||||
{
|
||||
rs.append(" - ").append(error).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
// print warnings if there are any //
|
||||
/////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(warnings))
|
||||
{
|
||||
rs.append("\nAssessment found the following ").append(StringUtils.plural(warnings, "warning", "warnings")).append(": \uD83E\uDD28\n");
|
||||
|
||||
for(String warning : warnings)
|
||||
{
|
||||
rs.append(" - ").append(warning).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// print suggestions, if there were any //
|
||||
//////////////////////////////////////////
|
||||
if(CollectionUtils.nullSafeHasContents(suggestions))
|
||||
{
|
||||
rs.append("\nThe following ").append(StringUtils.plural(suggestions, "fix is", "fixes are")).append(" suggested: \uD83E\uDD13\n");
|
||||
|
||||
for(String suggestion : suggestions)
|
||||
{
|
||||
rs.append("\n").append(suggestion).append("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
return (rs.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for qInstance
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QInstance getInstance()
|
||||
{
|
||||
return qInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for errors
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<String> getErrors()
|
||||
{
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for warnings
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<String> getWarnings()
|
||||
{
|
||||
return warnings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addError(String errorMessage)
|
||||
{
|
||||
errors.add(errorMessage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addWarning(String warningMessage)
|
||||
{
|
||||
warnings.add(warningMessage);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addError(String errorMessage, Exception e)
|
||||
{
|
||||
addError(errorMessage + " : " + e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void addSuggestion(String message)
|
||||
{
|
||||
suggestions.add(message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public int getExitCode()
|
||||
{
|
||||
if(CollectionUtils.nullSafeHasContents(errors))
|
||||
{
|
||||
return (1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
}
|
@ -37,4 +37,13 @@ public interface QInstanceEnricherPluginInterface<T>
|
||||
*******************************************************************************/
|
||||
void enrich(T object, QInstance qInstance);
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default String getPluginIdentifier()
|
||||
{
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,510 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.loaders;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.YamlUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import static com.kingsrook.qqq.backend.core.utils.ValueUtils.getValueAsInteger;
|
||||
import static com.kingsrook.qqq.backend.core.utils.ValueUtils.getValueAsString;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Abstract base class in hierarchy of classes that know how to construct &
|
||||
** populate QMetaDataObject instances, based on input streams (e.g., from files).
|
||||
*******************************************************************************/
|
||||
public abstract class AbstractMetaDataLoader<T extends QMetaDataObject>
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(AbstractMetaDataLoader.class);
|
||||
|
||||
private String fileName;
|
||||
|
||||
private List<LoadingProblem> problems = new ArrayList<>();
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public T fileToMetaDataObject(QInstance qInstance, InputStream inputStream, String fileName) throws QMetaDataLoaderException
|
||||
{
|
||||
this.fileName = fileName;
|
||||
Map<String, Object> map = fileToMap(inputStream, fileName);
|
||||
LoadingContext loadingContext = new LoadingContext(fileName, "/");
|
||||
return (mapToMetaDataObject(qInstance, map, loadingContext));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public abstract T mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
protected Map<String, Object> fileToMap(InputStream inputStream, String fileName) throws QMetaDataLoaderException
|
||||
{
|
||||
try
|
||||
{
|
||||
String string = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
|
||||
string = StringUtils.ltrim(string);
|
||||
if(fileName.toLowerCase().endsWith(".json"))
|
||||
{
|
||||
return JsonUtils.toObject(string, new TypeReference<>() {});
|
||||
}
|
||||
else if(fileName.toLowerCase().endsWith(".yaml") || fileName.toLowerCase().endsWith(".yml"))
|
||||
{
|
||||
return YamlUtils.toMap(string);
|
||||
}
|
||||
|
||||
throw (new QMetaDataLoaderException("Unsupported file format (based on file name: " + fileName + ")"));
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
throw new QMetaDataLoaderException("Error building map from file: " + fileName, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
protected void reflectivelyMap(QInstance qInstance, QMetaDataObject targetObject, Map<String, Object> map, LoadingContext context)
|
||||
{
|
||||
Class<? extends QMetaDataObject> targetClass = targetObject.getClass();
|
||||
Set<String> usedFieldNames = new HashSet<>();
|
||||
|
||||
for(Method method : targetClass.getMethods())
|
||||
{
|
||||
try
|
||||
{
|
||||
if(method.getName().startsWith("set") && method.getParameterTypes().length == 1)
|
||||
{
|
||||
String propertyName = StringUtils.lcFirst(method.getName().substring(3));
|
||||
|
||||
if(map.containsKey(propertyName))
|
||||
{
|
||||
usedFieldNames.add(propertyName);
|
||||
Class<?> parameterType = method.getParameterTypes()[0];
|
||||
Object rawValue = map.get(propertyName);
|
||||
|
||||
try
|
||||
{
|
||||
Object mappedValue = reflectivelyMapValue(qInstance, method, parameterType, rawValue, context.descendToProperty(propertyName));
|
||||
method.invoke(targetObject, mappedValue);
|
||||
}
|
||||
catch(NoValueException nve)
|
||||
{
|
||||
///////////////////////
|
||||
// don't call setter //
|
||||
///////////////////////
|
||||
LOG.debug("at " + context + ": No value was mapped for property [" + propertyName + "] on " + targetClass.getSimpleName() + "." + method.getName() + ", raw value: [" + rawValue + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
addProblem(new LoadingProblem(context, "Error reflectively mapping on " + targetClass.getName() + "." + method.getName(), e));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// mmm, slightly sus... //
|
||||
//////////////////////////
|
||||
map.remove("class");
|
||||
map.remove("version");
|
||||
|
||||
Set<String> unrecognizedKeys = new HashSet<>(map.keySet());
|
||||
unrecognizedKeys.removeAll(usedFieldNames);
|
||||
|
||||
if(!unrecognizedKeys.isEmpty())
|
||||
{
|
||||
addProblem(new LoadingProblem(context, unrecognizedKeys.size() + " Unrecognized " + StringUtils.plural(unrecognizedKeys, "property", "properties") + ": " + unrecognizedKeys));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
public Object reflectivelyMapValue(QInstance qInstance, Method method, Class<?> parameterType, Object rawValue, LoadingContext context) throws Exception
|
||||
{
|
||||
if(rawValue instanceof String s && s.matches("^\\$\\{.+\\..+}"))
|
||||
{
|
||||
rawValue = new QMetaDataVariableInterpreter().interpret(s);
|
||||
LOG.debug("Interpreted raw value [" + s + "] as [" + StringUtils.maskAndTruncate(ValueUtils.getValueAsString(rawValue) + "]"));
|
||||
}
|
||||
|
||||
if(parameterType.equals(String.class))
|
||||
{
|
||||
return (getValueAsString(rawValue));
|
||||
}
|
||||
else if(parameterType.equals(Integer.class))
|
||||
{
|
||||
try
|
||||
{
|
||||
return (getValueAsInteger(rawValue));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
addProblem(new LoadingProblem(context, "[" + rawValue + "] is not an Integer value."));
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(Boolean.class))
|
||||
{
|
||||
if("true".equals(rawValue) || Boolean.TRUE.equals(rawValue))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
else if("false".equals(rawValue) || Boolean.FALSE.equals(rawValue))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
else if(rawValue == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
else
|
||||
{
|
||||
addProblem(new LoadingProblem(context, "[" + rawValue + "] is not a boolean value (must be 'true' or 'false')."));
|
||||
return (null);
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(boolean.class))
|
||||
{
|
||||
if("true".equals(rawValue) || Boolean.TRUE.equals(rawValue))
|
||||
{
|
||||
return (true);
|
||||
}
|
||||
else if("false".equals(rawValue) || Boolean.FALSE.equals(rawValue))
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
else
|
||||
{
|
||||
addProblem(new LoadingProblem(context, rawValue + " is not a boolean value (must be 'true' or 'false')."));
|
||||
throw (new NoValueException());
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(List.class))
|
||||
{
|
||||
Type actualTypeArgument = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
|
||||
Class<?> actualTypeClass = Class.forName(actualTypeArgument.getTypeName());
|
||||
|
||||
if(rawValue instanceof @SuppressWarnings("rawtypes")List valueList)
|
||||
{
|
||||
List<Object> mappedValueList = new ArrayList<>();
|
||||
for(Object o : valueList)
|
||||
{
|
||||
try
|
||||
{
|
||||
Object mappedValue = reflectivelyMapValue(qInstance, null, actualTypeClass, o, context);
|
||||
mappedValueList.add(mappedValue);
|
||||
}
|
||||
catch(NoValueException nve)
|
||||
{
|
||||
// leave off list
|
||||
}
|
||||
}
|
||||
return (mappedValueList);
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(Set.class))
|
||||
{
|
||||
Type actualTypeArgument = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
|
||||
Class<?> actualTypeClass = Class.forName(actualTypeArgument.getTypeName());
|
||||
|
||||
if(rawValue instanceof @SuppressWarnings("rawtypes")List valueList)
|
||||
{
|
||||
Set<Object> mappedValueSet = new LinkedHashSet<>();
|
||||
for(Object o : valueList)
|
||||
{
|
||||
try
|
||||
{
|
||||
Object mappedValue = reflectivelyMapValue(qInstance, null, actualTypeClass, o, context);
|
||||
mappedValueSet.add(mappedValue);
|
||||
}
|
||||
catch(NoValueException nve)
|
||||
{
|
||||
// leave off list
|
||||
}
|
||||
}
|
||||
return (mappedValueSet);
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(Map.class))
|
||||
{
|
||||
Type keyType = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[0];
|
||||
if(!keyType.equals(String.class))
|
||||
{
|
||||
addProblem(new LoadingProblem(context, "Unsupported key type for " + method + " got [" + keyType + "], expected [String]"));
|
||||
throw new NoValueException();
|
||||
}
|
||||
// todo make sure string
|
||||
|
||||
Type actualTypeArgument = ((ParameterizedType) method.getGenericParameterTypes()[0]).getActualTypeArguments()[1];
|
||||
Class<?> actualTypeClass = Class.forName(actualTypeArgument.getTypeName());
|
||||
|
||||
if(rawValue instanceof @SuppressWarnings("rawtypes")Map valueMap)
|
||||
{
|
||||
Map<String, Object> mappedValueMap = new LinkedHashMap<>();
|
||||
for(Object o : valueMap.entrySet())
|
||||
{
|
||||
try
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Map.Entry<String, Object> entry = (Map.Entry<String, Object>) o;
|
||||
Object mappedValue = reflectivelyMapValue(qInstance, null, actualTypeClass, entry.getValue(), context);
|
||||
mappedValueMap.put(entry.getKey(), mappedValue);
|
||||
}
|
||||
catch(NoValueException nve)
|
||||
{
|
||||
// leave out of map
|
||||
}
|
||||
}
|
||||
return (mappedValueMap);
|
||||
}
|
||||
}
|
||||
else if(parameterType.isEnum())
|
||||
{
|
||||
String value = getValueAsString(rawValue);
|
||||
for(Object enumConstant : parameterType.getEnumConstants())
|
||||
{
|
||||
if(((Enum<?>) enumConstant).name().equals(value))
|
||||
{
|
||||
return (enumConstant);
|
||||
}
|
||||
}
|
||||
|
||||
addProblem(new LoadingProblem(context, "Unrecognized value [" + rawValue + "]. Expected one of: " + Arrays.toString(parameterType.getEnumConstants())));
|
||||
}
|
||||
else if(MetaDataLoaderRegistry.hasLoaderForClass(parameterType))
|
||||
{
|
||||
if(rawValue instanceof @SuppressWarnings("rawtypes")Map valueMap)
|
||||
{
|
||||
Class<? extends AbstractMetaDataLoader<?>> loaderClass = MetaDataLoaderRegistry.getLoaderForClass(parameterType);
|
||||
AbstractMetaDataLoader<?> loader = loaderClass.getConstructor().newInstance();
|
||||
//noinspection unchecked
|
||||
return (loader.mapToMetaDataObject(qInstance, valueMap, context));
|
||||
}
|
||||
}
|
||||
else if(QMetaDataObject.class.isAssignableFrom(parameterType))
|
||||
{
|
||||
if(rawValue instanceof @SuppressWarnings("rawtypes")Map valueMap)
|
||||
{
|
||||
QMetaDataObject childObject = (QMetaDataObject) parameterType.getConstructor().newInstance();
|
||||
//noinspection unchecked
|
||||
reflectivelyMap(qInstance, childObject, valueMap, context);
|
||||
return (childObject);
|
||||
}
|
||||
}
|
||||
else if(parameterType.equals(Serializable.class))
|
||||
{
|
||||
if(rawValue instanceof String
|
||||
|| rawValue instanceof Integer
|
||||
|| rawValue instanceof BigDecimal
|
||||
|| rawValue instanceof Boolean
|
||||
)
|
||||
{
|
||||
return rawValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo clean up this message/level
|
||||
addProblem(new LoadingProblem(context, "No case for " + parameterType + " (arg to: " + method + ")"));
|
||||
}
|
||||
|
||||
throw new NoValueException();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// unclear if the below is needed. if so, useful to not re-write, but is hurting test coverage, so zombie until used //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///***************************************************************************
|
||||
// *
|
||||
// ***************************************************************************/
|
||||
//protected ListOfMapOrMapOfMap getListOfMapOrMapOfMap(Map<String, Object> map, String key)
|
||||
//{
|
||||
// if(map.containsKey(key))
|
||||
// {
|
||||
// if(map.get(key) instanceof List)
|
||||
// {
|
||||
// return (new ListOfMapOrMapOfMap((List<Map<String, Object>>) map.get(key)));
|
||||
// }
|
||||
// else if(map.get(key) instanceof Map)
|
||||
// {
|
||||
// return (new ListOfMapOrMapOfMap((Map<String, Map<String, Object>>) map.get(key)));
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// LOG.warn("Expected list or map under key [" + key + "] while processing [" + getClass().getSimpleName() + "] from [" + fileName + "], but found: " + (map.get(key) == null ? "null" : map.get(key).getClass().getSimpleName()));
|
||||
// }
|
||||
// }
|
||||
|
||||
// return (null);
|
||||
//}
|
||||
|
||||
///***************************************************************************
|
||||
// *
|
||||
// ***************************************************************************/
|
||||
//protected List<Map<String, Object>> getListOfMap(Map<String, Object> map, String key)
|
||||
//{
|
||||
// if(map.containsKey(key))
|
||||
// {
|
||||
// if(map.get(key) instanceof List)
|
||||
// {
|
||||
// return (List<Map<String, Object>>) map.get(key);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// LOG.warn("Expected list under key [" + key + "] while processing [" + getClass().getSimpleName() + "] from [" + fileName + "], but found: " + (map.get(key) == null ? "null" : map.get(key).getClass().getSimpleName()));
|
||||
// }
|
||||
// }
|
||||
|
||||
// return (null);
|
||||
//}
|
||||
|
||||
///***************************************************************************
|
||||
// *
|
||||
// ***************************************************************************/
|
||||
//protected Map<String, Map<String, Object>> getMapOfMap(Map<String, Object> map, String key)
|
||||
//{
|
||||
// if(map.containsKey(key))
|
||||
// {
|
||||
// if(map.get(key) instanceof Map)
|
||||
// {
|
||||
// return (Map<String, Map<String, Object>>) map.get(key);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// LOG.warn("Expected map under key [" + key + "] while processing [" + getClass().getSimpleName() + "] from [" + fileName + "], but found: " + (map.get(key) == null ? "null" : map.get(key).getClass().getSimpleName()));
|
||||
// }
|
||||
// }
|
||||
|
||||
// return (null);
|
||||
//}
|
||||
|
||||
///***************************************************************************
|
||||
// **
|
||||
// ***************************************************************************/
|
||||
//protected record ListOfMapOrMapOfMap(List<Map<String, Object>> listOf, Map<String, Map<String, Object>> mapOf)
|
||||
//{
|
||||
// /*******************************************************************************
|
||||
// ** Constructor
|
||||
// **
|
||||
// *******************************************************************************/
|
||||
// public ListOfMapOrMapOfMap(List<Map<String, Object>> listOf)
|
||||
// {
|
||||
// this(listOf, null);
|
||||
// }
|
||||
|
||||
// /*******************************************************************************
|
||||
// ** Constructor
|
||||
// **
|
||||
// *******************************************************************************/
|
||||
// public ListOfMapOrMapOfMap(Map<String, Map<String, Object>> mapOf)
|
||||
// {
|
||||
// this(null, mapOf);
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fileName
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getFileName()
|
||||
{
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
private static class NoValueException extends Exception
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public NoValueException()
|
||||
{
|
||||
super("No value");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public void addProblem(LoadingProblem problem)
|
||||
{
|
||||
problems.add(problem);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for problems
|
||||
**
|
||||
*******************************************************************************/
|
||||
public List<LoadingProblem> getProblems()
|
||||
{
|
||||
return (problems);
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.loaders;
|
||||
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.implementations.GenericMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.AnyKey;
|
||||
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Generic implementation of AbstractMetaDataLoader, who "detects" the class
|
||||
** of meta data object to be created, then defers to an appropriate subclass
|
||||
** to do the work.
|
||||
*******************************************************************************/
|
||||
public class ClassDetectingMetaDataLoader extends AbstractMetaDataLoader<QMetaDataObject>
|
||||
{
|
||||
private static final Memoization<AnyKey, List<Class<?>>> memoizedMetaDataObjectClasses = new Memoization<>();
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
public AbstractMetaDataLoader<?> getLoaderForFile(InputStream inputStream, String fileName) throws QMetaDataLoaderException
|
||||
{
|
||||
Map<String, Object> map = fileToMap(inputStream, fileName);
|
||||
return (getLoaderForMap(map));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
public AbstractMetaDataLoader<?> getLoaderForMap(Map<String, Object> map) throws QMetaDataLoaderException
|
||||
{
|
||||
if(map.containsKey("class"))
|
||||
{
|
||||
String classProperty = ValueUtils.getValueAsString(map.get("class"));
|
||||
try
|
||||
{
|
||||
if(MetaDataLoaderRegistry.hasLoaderForSimpleName(classProperty))
|
||||
{
|
||||
Class<? extends AbstractMetaDataLoader<?>> loaderClass = MetaDataLoaderRegistry.getLoaderForSimpleName(classProperty);
|
||||
return (loaderClass.getConstructor().newInstance());
|
||||
}
|
||||
else
|
||||
{
|
||||
Optional<List<Class<?>>> metaDataClasses = memoizedMetaDataObjectClasses.getResult(AnyKey.getInstance(), k -> ClassPathUtils.getClassesContainingNameAndOfType("MetaData", QMetaDataObject.class));
|
||||
if(metaDataClasses.isEmpty())
|
||||
{
|
||||
throw (new QMetaDataLoaderException("Could not get list of metaDataObjects from class loader"));
|
||||
}
|
||||
|
||||
for(Class<?> c : metaDataClasses.get())
|
||||
{
|
||||
if(c.getSimpleName().equals(classProperty) && QMetaDataObject.class.isAssignableFrom(c))
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends QMetaDataObject> metaDataClass = (Class<? extends QMetaDataObject>) c;
|
||||
return new GenericMetaDataLoader<>(metaDataClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new QMetaDataLoaderException("Unexpected class [" + classProperty + "] (not a QMetaDataObject; doesn't have a registered MetaDataLoader) specified in " + getFileName());
|
||||
}
|
||||
catch(QMetaDataLoaderException qmdle)
|
||||
{
|
||||
throw (qmdle);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw new QMetaDataLoaderException("Error handling class [" + classProperty + "] specified in " + getFileName(), e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new QMetaDataLoaderException("Cannot detect meta-data type, because [class] attribute was not specified in file: " + getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QMetaDataObject mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
|
||||
{
|
||||
AbstractMetaDataLoader<?> loaderForMap = getLoaderForMap(map);
|
||||
return loaderForMap.mapToMetaDataObject(qInstance, map, context);
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.loaders;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Record to track where loader objects are - e.g., what file they're on,
|
||||
** and at what property path within the file (e.g., helps report problems).
|
||||
*******************************************************************************/
|
||||
public record LoadingContext(String fileName, String propertyPath)
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public LoadingContext descendToProperty(String propertyName)
|
||||
{
|
||||
return new LoadingContext(fileName, propertyPath + propertyName + "/");
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.loaders;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** record that tracks a problem that was encountered when loading files.
|
||||
*******************************************************************************/
|
||||
public record LoadingProblem(LoadingContext context, String message, Exception exception) // todo Level if useful
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public LoadingProblem(LoadingContext context, String message)
|
||||
{
|
||||
this(context, message, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "at[" + context.fileName() + "][" + context.propertyPath() + "]: " + message;
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.loaders;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.Pair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** class that loads a directory full of meta data files into meta data objects,
|
||||
** and then sets all of them in a QInstance.
|
||||
*******************************************************************************/
|
||||
public class MetaDataLoaderHelper
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(MetaDataLoaderHelper.class);
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
public static void processAllMetaDataFilesInDirectory(QInstance qInstance, String path) throws QException
|
||||
{
|
||||
List<Pair<File, AbstractMetaDataLoader<?>>> loaders = new ArrayList<>();
|
||||
|
||||
File directory = new File(path);
|
||||
processAllMetaDataFilesInDirectory(loaders, directory);
|
||||
|
||||
// todo - some version of sorting the loaders by type or possibly a sort field within the files (or file names)
|
||||
|
||||
for(Pair<File, AbstractMetaDataLoader<?>> pair : loaders)
|
||||
{
|
||||
File file = pair.getA();
|
||||
AbstractMetaDataLoader<?> loader = pair.getB();
|
||||
try(FileInputStream fileInputStream = new FileInputStream(file))
|
||||
{
|
||||
QMetaDataObject qMetaDataObject = loader.fileToMetaDataObject(qInstance, fileInputStream, file.getName());
|
||||
|
||||
if(CollectionUtils.nullSafeHasContents(loader.getProblems()))
|
||||
{
|
||||
loader.getProblems().forEach(System.out::println);
|
||||
}
|
||||
|
||||
if(qMetaDataObject instanceof TopLevelMetaDataInterface topLevelMetaData)
|
||||
{
|
||||
topLevelMetaData.addSelfToInstance(qInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.warn("Received a non-topLevelMetaDataObject from file: " + file.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error processing file: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
private static void processAllMetaDataFilesInDirectory(List<Pair<File, AbstractMetaDataLoader<?>>> loaders, File directory) throws QException
|
||||
{
|
||||
for(File file : Objects.requireNonNullElse(directory.listFiles(), new File[0]))
|
||||
{
|
||||
if(file.isDirectory())
|
||||
{
|
||||
processAllMetaDataFilesInDirectory(loaders, file);
|
||||
}
|
||||
else
|
||||
{
|
||||
try(FileInputStream fileInputStream = new FileInputStream(file))
|
||||
{
|
||||
AbstractMetaDataLoader<?> loader = new ClassDetectingMetaDataLoader().getLoaderForFile(fileInputStream, file.getName());
|
||||
loaders.add(Pair.of(file, loader));
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error processing file: " + file.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.loaders;
|
||||
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.implementations.QTableMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class MetaDataLoaderRegistry
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(AbstractMetaDataLoader.class);
|
||||
|
||||
private static final Map<Class<?>, Class<? extends AbstractMetaDataLoader<?>>> registeredLoaders = new HashMap<>();
|
||||
private static final Map<String, Class<? extends AbstractMetaDataLoader<?>>> registeredLoadersByTargetSimpleName = new HashMap<>();
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
{
|
||||
List<Class<?>> classesInPackage = ClassPathUtils.getClassesInPackage(QTableMetaDataLoader.class.getPackageName());
|
||||
for(Class<?> possibleLoaderClass : classesInPackage)
|
||||
{
|
||||
try
|
||||
{
|
||||
Type superClass = possibleLoaderClass.getGenericSuperclass();
|
||||
if(superClass.getTypeName().startsWith(AbstractMetaDataLoader.class.getName() + "<"))
|
||||
{
|
||||
Type actualTypeArgument = ((ParameterizedType) superClass).getActualTypeArguments()[0];
|
||||
if(actualTypeArgument instanceof Class)
|
||||
{
|
||||
//noinspection unchecked
|
||||
Class<? extends AbstractMetaDataLoader<?>> loaderClass = (Class<? extends AbstractMetaDataLoader<?>>) possibleLoaderClass;
|
||||
|
||||
Class<?> metaDataObjectType = Class.forName(actualTypeArgument.getTypeName());
|
||||
registeredLoaders.put(metaDataObjectType, loaderClass);
|
||||
registeredLoadersByTargetSimpleName.put(metaDataObjectType.getSimpleName(), loaderClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Error on class: " + possibleLoaderClass, e);
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Registered loaders: " + registeredLoadersByTargetSimpleName);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.error("Error in static init block for MetaDataLoaderRegistry", e);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static boolean hasLoaderForClass(Class<?> metaDataClass)
|
||||
{
|
||||
return registeredLoaders.containsKey(metaDataClass);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static Class<? extends AbstractMetaDataLoader<?>> getLoaderForClass(Class<?> metaDataClass)
|
||||
{
|
||||
return registeredLoaders.get(metaDataClass);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static boolean hasLoaderForSimpleName(String targetSimpleName)
|
||||
{
|
||||
return registeredLoadersByTargetSimpleName.containsKey(targetSimpleName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static Class<? extends AbstractMetaDataLoader<?>> getLoaderForSimpleName(String targetSimpleName)
|
||||
{
|
||||
return registeredLoadersByTargetSimpleName.get(targetSimpleName);
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.loaders;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QMetaDataLoaderException extends Exception
|
||||
{
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QMetaDataLoaderException(String message)
|
||||
{
|
||||
super(message);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QMetaDataLoaderException(String message, Throwable cause)
|
||||
{
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.loaders.implementations;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.AbstractMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.LoadingContext;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.QMetaDataLoaderException;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class GenericMetaDataLoader<T extends QMetaDataObject> extends AbstractMetaDataLoader<T>
|
||||
{
|
||||
private final Class<T> metaDataClass;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor
|
||||
**
|
||||
*******************************************************************************/
|
||||
public GenericMetaDataLoader(Class<T> metaDataClass)
|
||||
{
|
||||
this.metaDataClass = metaDataClass;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public T mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
|
||||
{
|
||||
try
|
||||
{
|
||||
T object = metaDataClass.getConstructor().newInstance();
|
||||
reflectivelyMap(qInstance, object, map, context);
|
||||
return (object);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QMetaDataLoaderException("Error loading metaData object of type " + metaDataClass.getSimpleName(), e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.loaders.implementations;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.AbstractMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.LoadingContext;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.QMetaDataLoaderException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QStepDataLoader extends AbstractMetaDataLoader<QStepMetaData>
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QStepDataLoader.class);
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QStepMetaData mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
|
||||
{
|
||||
String stepType = ValueUtils.getValueAsString(map.get("stepType"));
|
||||
|
||||
if(!StringUtils.hasContent(stepType))
|
||||
{
|
||||
throw (new QMetaDataLoaderException("stepType was not specified for process step"));
|
||||
}
|
||||
|
||||
QStepMetaData step;
|
||||
if("backend".equalsIgnoreCase(stepType))
|
||||
{
|
||||
step = new QBackendStepMetaData();
|
||||
reflectivelyMap(qInstance, step, map, context);
|
||||
}
|
||||
else if("frontend".equalsIgnoreCase(stepType))
|
||||
{
|
||||
step = new QFrontendStepMetaData();
|
||||
reflectivelyMap(qInstance, step, map, context);
|
||||
}
|
||||
// todo - we have custom factory methods for this, so, maybe needs all custom loader?
|
||||
// else if("stateMachine".equalsIgnoreCase(stepType))
|
||||
// {
|
||||
// step = new QStateMachineStep();
|
||||
// reflectivelyMap(qInstance, step, map, context);
|
||||
// }
|
||||
else
|
||||
{
|
||||
throw (new QMetaDataLoaderException("Unsupported step stepType: " + stepType));
|
||||
}
|
||||
|
||||
return (step);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.instances.loaders.implementations;
|
||||
|
||||
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.AbstractMetaDataLoader;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.LoadingContext;
|
||||
import com.kingsrook.qqq.backend.core.instances.loaders.QMetaDataLoaderException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QTableMetaDataLoader extends AbstractMetaDataLoader<QTableMetaData>
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QTableMetaDataLoader.class);
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QTableMetaData mapToMetaDataObject(QInstance qInstance, Map<String, Object> map, LoadingContext context) throws QMetaDataLoaderException
|
||||
{
|
||||
QTableMetaData table = new QTableMetaData();
|
||||
|
||||
reflectivelyMap(qInstance, table, map, context);
|
||||
|
||||
// todo - handle QTableBackendDetails, based on backend's type
|
||||
|
||||
return (table);
|
||||
}
|
||||
|
||||
}
|
@ -38,4 +38,13 @@ public interface QInstanceValidatorPluginInterface<T>
|
||||
*******************************************************************************/
|
||||
void validate(T object, QInstance qInstance, QInstanceValidator qInstanceValidator);
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
default String getPluginIdentifier()
|
||||
{
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,11 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -31,6 +35,45 @@ import com.kingsrook.qqq.backend.core.logging.LogPair;
|
||||
*******************************************************************************/
|
||||
public interface ProcessSummaryLineInterface extends Serializable
|
||||
{
|
||||
QLogger LOG = QLogger.getLogger(ProcessSummaryLineInterface.class);
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
static void log(String message, Serializable summaryLines, List<LogPair> additionalLogPairs)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(summaryLines instanceof List)
|
||||
{
|
||||
List<ProcessSummaryLineInterface> list = (List<ProcessSummaryLineInterface>) summaryLines;
|
||||
|
||||
List<LogPair> logPairs = new ArrayList<>();
|
||||
for(ProcessSummaryLineInterface processSummaryLineInterface : list)
|
||||
{
|
||||
LogPair logPair = processSummaryLineInterface.toLogPair();
|
||||
logPair.setKey(logPair.getKey() + logPairs.size());
|
||||
logPairs.add(logPair);
|
||||
}
|
||||
|
||||
if(additionalLogPairs != null)
|
||||
{
|
||||
logPairs.addAll(0, additionalLogPairs);
|
||||
}
|
||||
logPairs.add(0, logPair("message", message));
|
||||
|
||||
LOG.info(logPairs);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.info("Unrecognized type for summaryLines (expected List)", logPair("processSummary", summaryLines));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
LOG.info("Error logging a process summary", e, logPair("processSummary", summaryLines));
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for status
|
||||
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.processes;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QRuntimeException;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** base-class for bean-like classes to represent the fields of a process.
|
||||
** similar in spirit to QRecordEntity, but for processes.
|
||||
*******************************************************************************/
|
||||
public class QProcessPayload
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QProcessPayload.class);
|
||||
|
||||
private static final ListingHash<Class<? extends QProcessPayload>, QRecordEntityField> fieldMapping = new ListingHash<>();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Build an entity of this QRecord type from a QRecord
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static <T extends QProcessPayload> T fromProcessState(Class<T> c, ProcessState processState) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
T entity = c.getConstructor().newInstance();
|
||||
entity.populateFromProcessState(processState);
|
||||
return (entity);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error building process payload from state.", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
protected void populateFromProcessState(ProcessState processState)
|
||||
{
|
||||
try
|
||||
{
|
||||
List<QRecordEntityField> fieldList = getFieldList(this.getClass());
|
||||
|
||||
for(QRecordEntityField qRecordEntityField : fieldList)
|
||||
{
|
||||
Serializable value = processState.getValues().get(qRecordEntityField.getFieldName());
|
||||
Object typedValue = qRecordEntityField.convertValueType(value);
|
||||
qRecordEntityField.getSetter().invoke(this, typedValue);
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QRuntimeException("Error building process payload from process state.", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Copy the values from this payload into the given process state.
|
||||
** ALL fields in the entity will be set in the process state.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void toProcessState(ProcessState processState) throws QRuntimeException
|
||||
{
|
||||
try
|
||||
{
|
||||
for(QRecordEntityField qRecordEntityField : getFieldList(this.getClass()))
|
||||
{
|
||||
processState.getValues().put(qRecordEntityField.getFieldName(), (Serializable) qRecordEntityField.getGetter().invoke(this));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QRuntimeException("Error populating process state from process payload.", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
*
|
||||
***************************************************************************/
|
||||
public static Set<Class<?>> allowedFieldTypes()
|
||||
{
|
||||
HashSet<Class<?>> classes = new HashSet<>(ReflectiveBeanLikeClassUtils.defaultAllowedTypes());
|
||||
classes.add(Map.class);
|
||||
classes.add(List.class);
|
||||
return (classes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static List<QRecordEntityField> getFieldList(Class<? extends QProcessPayload> c)
|
||||
{
|
||||
if(!fieldMapping.containsKey(c))
|
||||
{
|
||||
List<QRecordEntityField> fieldList = new ArrayList<>();
|
||||
for(Method possibleGetter : c.getMethods())
|
||||
{
|
||||
if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, false, allowedFieldTypes()))
|
||||
{
|
||||
Optional<Method> setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
|
||||
|
||||
if(setter.isPresent())
|
||||
{
|
||||
String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
|
||||
fieldList.add(new QRecordEntityField(fieldName, possibleGetter, setter.get(), possibleGetter.getReturnType(), null));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.debug("Getter method [" + possibleGetter.getName() + "] does not have a corresponding setter.");
|
||||
}
|
||||
}
|
||||
}
|
||||
fieldMapping.put(c, fieldList);
|
||||
}
|
||||
return (fieldMapping.get(c));
|
||||
}
|
||||
|
||||
}
|
@ -628,4 +628,15 @@ public class RunBackendStepInput extends AbstractActionInput
|
||||
{
|
||||
return (QContext.getQInstance().getProcess(getProcessName()));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** return a QProcessPayload subclass instance, with values populated from
|
||||
** the current process state.
|
||||
***************************************************************************/
|
||||
public <T extends QProcessPayload> T getProcessPayload(Class<T> payloadClass) throws QException
|
||||
{
|
||||
return QProcessPayload.fromProcessState(payloadClass, getProcessState());
|
||||
}
|
||||
}
|
||||
|
@ -445,4 +445,14 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
|
||||
this.processState.setProcessMetaDataAdjustment(processMetaDataAdjustment);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** Update the process state with values from the input processPayload
|
||||
** subclass instance.
|
||||
***************************************************************************/
|
||||
public void setProcessPayload(QProcessPayload processPayload)
|
||||
{
|
||||
processPayload.toProcessState(getProcessState());
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.actions.reporting.ExportStreamerInterface;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportMetaData;
|
||||
|
||||
|
||||
@ -44,6 +45,7 @@ public class ReportInput extends AbstractTableActionInput
|
||||
private ReportDestination reportDestination;
|
||||
|
||||
private Supplier<? extends ExportStreamerInterface> overrideExportStreamerSupplier;
|
||||
private QCodeReference exportStyleCustomizer;
|
||||
|
||||
|
||||
|
||||
@ -208,4 +210,35 @@ public class ReportInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for exportStyleCustomizer
|
||||
*******************************************************************************/
|
||||
public QCodeReference getExportStyleCustomizer()
|
||||
{
|
||||
return (this.exportStyleCustomizer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for exportStyleCustomizer
|
||||
*******************************************************************************/
|
||||
public void setExportStyleCustomizer(QCodeReference exportStyleCustomizer)
|
||||
{
|
||||
this.exportStyleCustomizer = exportStyleCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for exportStyleCustomizer
|
||||
*******************************************************************************/
|
||||
public ReportInput withExportStyleCustomizer(QCodeReference exportStyleCustomizer)
|
||||
{
|
||||
this.exportStyleCustomizer = exportStyleCustomizer;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface to mark enums (presumably classes too, but the original intent is
|
||||
** enums) that can be added to insert/update/delete action inputs to flag behaviors
|
||||
*******************************************************************************/
|
||||
public interface ActionFlag extends Serializable
|
||||
{
|
||||
|
||||
}
|
@ -24,9 +24,12 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.delete;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
@ -47,6 +50,8 @@ public class DeleteInput extends AbstractTableActionInput
|
||||
private boolean omitDmlAudit = false;
|
||||
private String auditContext = null;
|
||||
|
||||
private Set<ActionFlag> flags;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -295,4 +300,65 @@ public class DeleteInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for flags
|
||||
*******************************************************************************/
|
||||
public Set<ActionFlag> getFlags()
|
||||
{
|
||||
return (this.flags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for flags
|
||||
*******************************************************************************/
|
||||
public void setFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for flags
|
||||
*******************************************************************************/
|
||||
public DeleteInput withFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public DeleteInput withFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
this.flags = new HashSet<>();
|
||||
}
|
||||
this.flags.add(flag);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public boolean hasFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (this.flags.contains(flag));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,9 +23,12 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.insert;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -48,6 +51,8 @@ public class InsertInput extends AbstractTableActionInput
|
||||
private boolean omitDmlAudit = false;
|
||||
private String auditContext = null;
|
||||
|
||||
private Set<ActionFlag> flags;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -316,4 +321,65 @@ public class InsertInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for flags
|
||||
*******************************************************************************/
|
||||
public Set<ActionFlag> getFlags()
|
||||
{
|
||||
return (this.flags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for flags
|
||||
*******************************************************************************/
|
||||
public void setFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for flags
|
||||
*******************************************************************************/
|
||||
public InsertInput withFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public InsertInput withFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
this.flags = new HashSet<>();
|
||||
}
|
||||
this.flags.add(flag);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public boolean hasFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (this.flags.contains(flag));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import java.util.Set;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.serialization.QFilterCriteriaDeserializer;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
@ -42,7 +43,7 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
*
|
||||
*******************************************************************************/
|
||||
@JsonDeserialize(using = QFilterCriteriaDeserializer.class)
|
||||
public class QFilterCriteria implements Serializable, Cloneable
|
||||
public class QFilterCriteria implements Serializable, Cloneable, QMetaDataObject
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QFilterCriteria.class);
|
||||
|
||||
|
@ -23,13 +23,14 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.query;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Bean representing an element of a query order-by clause.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QFilterOrderBy implements Serializable, Cloneable
|
||||
public class QFilterOrderBy implements Serializable, Cloneable, QMetaDataObject
|
||||
{
|
||||
private String fieldName;
|
||||
private boolean isAscending = true;
|
||||
|
@ -36,6 +36,7 @@ import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
|
||||
import com.kingsrook.qqq.backend.core.logging.QLogger;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.AbstractFilterExpression;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.expressions.FilterVariableExpression;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
@ -45,7 +46,7 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
* Full "filter" for a query - a list of criteria and order-bys
|
||||
*
|
||||
*******************************************************************************/
|
||||
public class QQueryFilter implements Serializable, Cloneable
|
||||
public class QQueryFilter implements Serializable, Cloneable, QMetaDataObject
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QQueryFilter.class);
|
||||
|
||||
|
@ -22,9 +22,12 @@
|
||||
package com.kingsrook.qqq.backend.core.model.actions.tables.replace;
|
||||
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.UniqueKey;
|
||||
@ -39,12 +42,14 @@ public class ReplaceInput extends AbstractTableActionInput
|
||||
private UniqueKey key;
|
||||
private List<QRecord> records;
|
||||
private QQueryFilter filter;
|
||||
private boolean performDeletes = true;
|
||||
private boolean allowNullKeyValuesToEqual = false;
|
||||
private boolean setPrimaryKeyInInsertedRecords = false;
|
||||
private boolean performDeletes = true;
|
||||
private boolean allowNullKeyValuesToEqual = false;
|
||||
private boolean setPrimaryKeyInInsertedRecords = false;
|
||||
|
||||
private boolean omitDmlAudit = false;
|
||||
|
||||
private Set<ActionFlag> flags;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -303,4 +308,65 @@ public class ReplaceInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for flags
|
||||
*******************************************************************************/
|
||||
public Set<ActionFlag> getFlags()
|
||||
{
|
||||
return (this.flags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for flags
|
||||
*******************************************************************************/
|
||||
public void setFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for flags
|
||||
*******************************************************************************/
|
||||
public ReplaceInput withFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public ReplaceInput withFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
this.flags = new HashSet<>();
|
||||
}
|
||||
this.flags.add(flag);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public boolean hasFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (this.flags.contains(flag));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,9 +23,12 @@ package com.kingsrook.qqq.backend.core.model.actions.tables.update;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.ActionFlag;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.InputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.actions.tables.QInputSource;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
@ -56,6 +59,8 @@ public class UpdateInput extends AbstractTableActionInput
|
||||
private boolean omitModifyDateUpdate = false;
|
||||
private String auditContext = null;
|
||||
|
||||
private Set<ActionFlag> flags;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -385,4 +390,65 @@ public class UpdateInput extends AbstractTableActionInput
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for flags
|
||||
*******************************************************************************/
|
||||
public Set<ActionFlag> getFlags()
|
||||
{
|
||||
return (this.flags);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for flags
|
||||
*******************************************************************************/
|
||||
public void setFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for flags
|
||||
*******************************************************************************/
|
||||
public UpdateInput withFlags(Set<ActionFlag> flags)
|
||||
{
|
||||
this.flags = flags;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public UpdateInput withFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
this.flags = new HashSet<>();
|
||||
}
|
||||
this.flags.add(flag);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public boolean hasFlag(ActionFlag flag)
|
||||
{
|
||||
if(this.flags == null)
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
return (this.flags.contains(flag));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.common;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.function.Function;
|
||||
@ -47,7 +48,7 @@ public class TimeZonePossibleValueSourceMetaDataProvider
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource produce()
|
||||
{
|
||||
return (produce(null, null));
|
||||
return (produce(null, null, null));
|
||||
}
|
||||
|
||||
|
||||
@ -56,6 +57,16 @@ public class TimeZonePossibleValueSourceMetaDataProvider
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource produce(Predicate<String> filter, Function<String, String> labelMapper)
|
||||
{
|
||||
return (produce(filter, labelMapper, null));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QPossibleValueSource produce(Predicate<String> filter, Function<String, String> labelMapper, Comparator<QPossibleValue<?>> comparator)
|
||||
{
|
||||
QPossibleValueSource possibleValueSource = new QPossibleValueSource()
|
||||
.withName("timeZones")
|
||||
@ -72,6 +83,11 @@ public class TimeZonePossibleValueSourceMetaDataProvider
|
||||
}
|
||||
}
|
||||
|
||||
if(comparator != null)
|
||||
{
|
||||
enumValues.sort(comparator);
|
||||
}
|
||||
|
||||
possibleValueSource.withEnumValues(enumValues);
|
||||
return (possibleValueSource);
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ public enum WidgetType
|
||||
// record view/edit widgets //
|
||||
//////////////////////////////
|
||||
CHILD_RECORD_LIST("childRecordList"),
|
||||
CUSTOM_COMPONENT("customComponent"),
|
||||
DYNAMIC_FORM("dynamicForm"),
|
||||
DATA_BAG_VIEWER("dataBagViewer"),
|
||||
PIVOT_TABLE_SETUP("pivotTableSetup"),
|
||||
|
@ -468,7 +468,7 @@ public class QRecord implements Serializable
|
||||
*******************************************************************************/
|
||||
public String getValueString(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsString(values.get(fieldName)));
|
||||
return (ValueUtils.getValueAsString(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
@ -479,7 +479,7 @@ public class QRecord implements Serializable
|
||||
*******************************************************************************/
|
||||
public Integer getValueInteger(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsInteger(values.get(fieldName)));
|
||||
return (ValueUtils.getValueAsInteger(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
@ -490,7 +490,7 @@ public class QRecord implements Serializable
|
||||
*******************************************************************************/
|
||||
public Long getValueLong(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsLong(values.get(fieldName)));
|
||||
return (ValueUtils.getValueAsLong(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
@ -500,7 +500,7 @@ public class QRecord implements Serializable
|
||||
*******************************************************************************/
|
||||
public BigDecimal getValueBigDecimal(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsBigDecimal(values.get(fieldName)));
|
||||
return (ValueUtils.getValueAsBigDecimal(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
@ -510,7 +510,7 @@ public class QRecord implements Serializable
|
||||
*******************************************************************************/
|
||||
public Boolean getValueBoolean(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsBoolean(values.get(fieldName)));
|
||||
return (ValueUtils.getValueAsBoolean(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
@ -520,7 +520,7 @@ public class QRecord implements Serializable
|
||||
*******************************************************************************/
|
||||
public LocalTime getValueLocalTime(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsLocalTime(values.get(fieldName)));
|
||||
return (ValueUtils.getValueAsLocalTime(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
@ -530,7 +530,7 @@ public class QRecord implements Serializable
|
||||
*******************************************************************************/
|
||||
public LocalDate getValueLocalDate(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsLocalDate(values.get(fieldName)));
|
||||
return (ValueUtils.getValueAsLocalDate(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
@ -540,7 +540,7 @@ public class QRecord implements Serializable
|
||||
*******************************************************************************/
|
||||
public byte[] getValueByteArray(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsByteArray(values.get(fieldName)));
|
||||
return (ValueUtils.getValueAsByteArray(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
@ -550,7 +550,7 @@ public class QRecord implements Serializable
|
||||
*******************************************************************************/
|
||||
public Instant getValueInstant(String fieldName)
|
||||
{
|
||||
return (ValueUtils.getValueAsInstant(values.get(fieldName)));
|
||||
return (ValueUtils.getValueAsInstant(getValue(fieldName)));
|
||||
}
|
||||
|
||||
|
||||
|
@ -49,6 +49,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
|
||||
|
||||
@ -325,13 +326,13 @@ public abstract class QRecordEntity
|
||||
List<QRecordEntityField> fieldList = new ArrayList<>();
|
||||
for(Method possibleGetter : c.getMethods())
|
||||
{
|
||||
if(isGetter(possibleGetter))
|
||||
if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, true))
|
||||
{
|
||||
Optional<Method> setter = getSetterForGetter(c, possibleGetter);
|
||||
Optional<Method> setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
|
||||
|
||||
if(setter.isPresent())
|
||||
{
|
||||
String fieldName = getFieldNameFromGetter(possibleGetter);
|
||||
String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
|
||||
Optional<QField> fieldAnnotation = getQFieldAnnotation(c, fieldName);
|
||||
|
||||
if(fieldAnnotation.isPresent())
|
||||
@ -378,19 +379,19 @@ public abstract class QRecordEntity
|
||||
List<QRecordEntityAssociation> associationList = new ArrayList<>();
|
||||
for(Method possibleGetter : c.getMethods())
|
||||
{
|
||||
if(isGetter(possibleGetter))
|
||||
if(ReflectiveBeanLikeClassUtils.isGetter(possibleGetter, true))
|
||||
{
|
||||
Optional<Method> setter = getSetterForGetter(c, possibleGetter);
|
||||
Optional<Method> setter = ReflectiveBeanLikeClassUtils.getSetterForGetter(c, possibleGetter);
|
||||
|
||||
if(setter.isPresent())
|
||||
{
|
||||
String fieldName = getFieldNameFromGetter(possibleGetter);
|
||||
String fieldName = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(possibleGetter);
|
||||
Optional<QAssociation> associationAnnotation = getQAssociationAnnotation(c, fieldName);
|
||||
|
||||
if(associationAnnotation.isPresent())
|
||||
{
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends QRecordEntity> listTypeParam = (Class<? extends QRecordEntity>) getListTypeParam(possibleGetter.getReturnType(), possibleGetter.getAnnotatedReturnType());
|
||||
Class<? extends QRecordEntity> listTypeParam = (Class<? extends QRecordEntity>) ReflectiveBeanLikeClassUtils.getListTypeParam(possibleGetter.getReturnType(), possibleGetter.getAnnotatedReturnType());
|
||||
associationList.add(new QRecordEntityAssociation(fieldName, possibleGetter, setter.get(), listTypeParam, associationAnnotation.orElse(null)));
|
||||
}
|
||||
}
|
||||
@ -583,4 +584,31 @@ public abstract class QRecordEntity
|
||||
return (null);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static String getTableName(Class<? extends QRecordEntity> entityClass) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
Field tableNameField = entityClass.getDeclaredField("TABLE_NAME");
|
||||
String tableNameValue = (String) tableNameField.get(null);
|
||||
return (tableNameValue);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Could not get TABLE_NAME from entity class: " + entityClass.getSimpleName(), e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
** named without the 'get' to avoid conflict w/ entity fields named that...
|
||||
***************************************************************************/
|
||||
public String tableName() throws QException
|
||||
{
|
||||
return (getTableName(this.getClass()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
@ -170,6 +171,11 @@ public class QRecordEntityField
|
||||
{
|
||||
return (ValueUtils.getValueAsByteArray(value));
|
||||
}
|
||||
|
||||
if(type.equals(Map.class))
|
||||
{
|
||||
return (ValueUtils.getValueAsMap(value));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
@ -24,6 +24,7 @@ package com.kingsrook.qqq.backend.core.model.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@ -198,4 +199,62 @@ public class QRecordWithJoinedRecords extends QRecord
|
||||
return (rs);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public Map<String, List<QRecord>> getAssociatedRecords()
|
||||
{
|
||||
return mainRecord.getAssociatedRecords();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QRecord withAssociatedRecord(String name, QRecord associatedRecord)
|
||||
{
|
||||
mainRecord.withAssociatedRecord(name, associatedRecord);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QRecord withAssociatedRecords(Map<String, List<QRecord>> associatedRecords)
|
||||
{
|
||||
mainRecord.withAssociatedRecords(associatedRecords);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void setAssociatedRecords(Map<String, List<QRecord>> associatedRecords)
|
||||
{
|
||||
mainRecord.setAssociatedRecords(associatedRecords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QRecord withAssociatedRecords(String name, List<QRecord> associatedRecords)
|
||||
{
|
||||
mainRecord.withAssociatedRecords(name, associatedRecords);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -82,6 +82,7 @@ public class HelpContentMetaDataProvider
|
||||
table.getField("key").withFieldAdornment(AdornmentType.Size.LARGE.toAdornment());
|
||||
table.getField("content").withFieldAdornment(AdornmentType.Size.LARGE.toAdornment());
|
||||
table.getField("content").withFieldAdornment(new FieldAdornment(AdornmentType.CODE_EDITOR).withValue(AdornmentType.CodeEditorValues.languageMode("html")));
|
||||
table.getField("content").withGridColumns(12);
|
||||
|
||||
if(backendDetailEnricher != null)
|
||||
{
|
||||
|
@ -177,6 +177,18 @@ public class MetaDataProducerHelper
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// sort them by sort order, then by the type that they return, as set up in the static map //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////
|
||||
sortMetaDataProducers(producers);
|
||||
|
||||
return (producers);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public static void sortMetaDataProducers(List<MetaDataProducerInterface<?>> producers)
|
||||
{
|
||||
producers.sort(Comparator
|
||||
.comparing((MetaDataProducerInterface<?> p) -> p.getSortOrder())
|
||||
.thenComparing((MetaDataProducerInterface<?> p) ->
|
||||
@ -191,11 +203,10 @@ public class MetaDataProducerHelper
|
||||
return (0);
|
||||
}
|
||||
}));
|
||||
|
||||
return (producers);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Recursively find all classes in the given package, that implement MetaDataProducerInterface
|
||||
** run them, and add their output to the given qInstance.
|
||||
@ -417,7 +428,7 @@ public class MetaDataProducerHelper
|
||||
return (null);
|
||||
}
|
||||
|
||||
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName);
|
||||
ChildJoinFromRecordEntityGenericMetaDataProducer producer = new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName, childTable.childJoin().orderBy(), childTable.childJoin().isOneToOne());
|
||||
producer.setSourceClass(entityClass);
|
||||
return producer;
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ public class MetaDataProducerMultiOutput implements MetaDataProducerOutput, Sour
|
||||
{
|
||||
List<T> rs = new ArrayList<>();
|
||||
|
||||
for(MetaDataProducerOutput content : contents)
|
||||
for(MetaDataProducerOutput content : CollectionUtils.nonNullList(contents))
|
||||
{
|
||||
if(content instanceof MetaDataProducerMultiOutput multiOutput)
|
||||
{
|
||||
|
@ -28,6 +28,7 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
*******************************************************************************/
|
||||
public enum QAuthenticationType
|
||||
{
|
||||
OAUTH2("OAuth2"),
|
||||
AUTH_0("auth0"),
|
||||
TABLE_BASED("tableBased"),
|
||||
FULLY_ANONYMOUS("fullyAnonymous"),
|
||||
|
@ -23,6 +23,7 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@ -30,6 +31,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
|
||||
import com.kingsrook.qqq.backend.core.actions.metadata.MetaDataAction;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
@ -65,6 +67,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.security.QSecurityKeyType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.scheduler.schedulable.SchedulableType;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ListingHash;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import io.github.cdimascio.dotenv.Dotenv;
|
||||
import io.github.cdimascio.dotenv.DotenvEntry;
|
||||
@ -116,8 +119,13 @@ public class QInstance
|
||||
private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance();
|
||||
private QAuditRules defaultAuditRules = QAuditRules.defaultInstanceLevelNone();
|
||||
|
||||
private ListingHash<String, QCodeReference> tableCustomizers;
|
||||
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
private QCodeReference metaDataFilter = null;
|
||||
|
||||
private QCodeReference metaDataActionCustomizer = null;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// todo - lock down the object (no more changes allowed) after it's been validated? //
|
||||
// if doing so, may need to copy all of the collections into read-only versions... //
|
||||
@ -1247,7 +1255,7 @@ public class QInstance
|
||||
{
|
||||
this.supplementalMetaData = new HashMap<>();
|
||||
}
|
||||
this.supplementalMetaData.put(supplementalMetaData.getType(), supplementalMetaData);
|
||||
this.supplementalMetaData.put(supplementalMetaData.getName(), supplementalMetaData);
|
||||
return (this);
|
||||
}
|
||||
|
||||
@ -1495,6 +1503,7 @@ public class QInstance
|
||||
/*******************************************************************************
|
||||
** Getter for metaDataFilter
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
public QCodeReference getMetaDataFilter()
|
||||
{
|
||||
return (this.metaDataFilter);
|
||||
@ -1505,6 +1514,7 @@ public class QInstance
|
||||
/*******************************************************************************
|
||||
** Setter for metaDataFilter
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
public void setMetaDataFilter(QCodeReference metaDataFilter)
|
||||
{
|
||||
this.metaDataFilter = metaDataFilter;
|
||||
@ -1515,6 +1525,7 @@ public class QInstance
|
||||
/*******************************************************************************
|
||||
** Fluent setter for metaDataFilter
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrated to metaDataCustomizer")
|
||||
public QInstance withMetaDataFilter(QCodeReference metaDataFilter)
|
||||
{
|
||||
this.metaDataFilter = metaDataFilter;
|
||||
@ -1586,4 +1597,107 @@ public class QInstance
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for metaDataActionCustomizer
|
||||
*******************************************************************************/
|
||||
public QCodeReference getMetaDataActionCustomizer()
|
||||
{
|
||||
return (this.metaDataActionCustomizer);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for metaDataActionCustomizer
|
||||
*******************************************************************************/
|
||||
public void setMetaDataActionCustomizer(QCodeReference metaDataActionCustomizer)
|
||||
{
|
||||
this.metaDataActionCustomizer = metaDataActionCustomizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for metaDataActionCustomizer
|
||||
*******************************************************************************/
|
||||
public QInstance withMetaDataActionCustomizer(QCodeReference metaDataActionCustomizer)
|
||||
{
|
||||
this.metaDataActionCustomizer = metaDataActionCustomizer;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableCustomizers
|
||||
*******************************************************************************/
|
||||
public ListingHash<String, QCodeReference> getTableCustomizers()
|
||||
{
|
||||
return (this.tableCustomizers);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tableCustomizers
|
||||
*******************************************************************************/
|
||||
public void setTableCustomizers(ListingHash<String, QCodeReference> tableCustomizers)
|
||||
{
|
||||
this.tableCustomizers = tableCustomizers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tableCustomizers
|
||||
*******************************************************************************/
|
||||
public QInstance withTableCustomizers(ListingHash<String, QCodeReference> tableCustomizers)
|
||||
{
|
||||
this.tableCustomizers = tableCustomizers;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QInstance withTableCustomizer(String role, QCodeReference customizer)
|
||||
{
|
||||
if(this.tableCustomizers == null)
|
||||
{
|
||||
this.tableCustomizers = new ListingHash<>();
|
||||
}
|
||||
|
||||
this.tableCustomizers.add(role, customizer);
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QInstance withTableCustomizer(TableCustomizers tableCustomizer, QCodeReference customizer)
|
||||
{
|
||||
return (withTableCustomizer(tableCustomizer.getRole(), customizer));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tableCustomizers
|
||||
*******************************************************************************/
|
||||
public List<QCodeReference> getTableCustomizers(TableCustomizers tableCustomizer)
|
||||
{
|
||||
if(this.tableCustomizers == null)
|
||||
{
|
||||
return (Collections.emptyList());
|
||||
}
|
||||
|
||||
return (this.tableCustomizers.getOrDefault(tableCustomizer.getRole(), Collections.emptyList()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface common among all objects that can be considered qqq meta data -
|
||||
** e.g., stored in a QInstance.
|
||||
*******************************************************************************/
|
||||
public interface QMetaDataObject extends Serializable
|
||||
{
|
||||
}
|
@ -22,28 +22,21 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
|
||||
|
||||
import java.util.function.Supplier;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Base-class for instance-level meta-data defined by some supplemental module, etc,
|
||||
** outside of qqq core
|
||||
*******************************************************************************/
|
||||
public abstract class QSupplementalInstanceMetaData implements TopLevelMetaDataInterface
|
||||
public interface QSupplementalInstanceMetaData extends TopLevelMetaDataInterface
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for type
|
||||
*******************************************************************************/
|
||||
public abstract String getType();
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void enrich(QTableMetaData table)
|
||||
default void enrich(QInstance qInstance)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
@ -55,7 +48,7 @@ public abstract class QSupplementalInstanceMetaData implements TopLevelMetaDataI
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void validate(QInstance qInstance, QInstanceValidator validator)
|
||||
default void validate(QInstance qInstance, QInstanceValidator validator)
|
||||
{
|
||||
////////////////////////
|
||||
// noop in base class //
|
||||
@ -68,9 +61,33 @@ public abstract class QSupplementalInstanceMetaData implements TopLevelMetaDataI
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void addSelfToInstance(QInstance qInstance)
|
||||
default void addSelfToInstance(QInstance qInstance)
|
||||
{
|
||||
qInstance.withSupplementalMetaData(this);
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
static <S extends QSupplementalInstanceMetaData> S of(QInstance qInstance, String name)
|
||||
{
|
||||
return ((S) qInstance.getSupplementalMetaData(name));
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
static <S extends QSupplementalInstanceMetaData> S ofOrWithNew(QInstance qInstance, String name, Supplier<S> supplier)
|
||||
{
|
||||
S s = (S) qInstance.getSupplementalMetaData(name);
|
||||
if(s == null)
|
||||
{
|
||||
s = supplier.get();
|
||||
s.addSelfToInstance(qInstance);
|
||||
}
|
||||
return (s);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ package com.kingsrook.qqq.backend.core.model.metadata;
|
||||
** Interface for meta-data classes that can be added directly (e.g, at the top
|
||||
** level) to a QInstance (such as a QTableMetaData - not a QFieldMetaData).
|
||||
*******************************************************************************/
|
||||
public interface TopLevelMetaDataInterface extends MetaDataProducerOutput
|
||||
public interface TopLevelMetaDataInterface extends MetaDataProducerOutput, QMetaDataObject
|
||||
{
|
||||
|
||||
/*******************************************************************************
|
||||
|
@ -22,10 +22,13 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.audits;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QAuditRules
|
||||
public class QAuditRules implements QMetaDataObject
|
||||
{
|
||||
private AuditLevel auditLevel;
|
||||
|
||||
|
@ -0,0 +1,320 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.authentication;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleDispatcher;
|
||||
import com.kingsrook.qqq.backend.core.modules.authentication.implementations.OAuth2AuthenticationModule;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Meta-data to provide details of an OAuth2 Authentication module
|
||||
*******************************************************************************/
|
||||
public class OAuth2AuthenticationMetaData extends QAuthenticationMetaData
|
||||
{
|
||||
private String baseUrl;
|
||||
private String tokenUrl;
|
||||
private String clientId;
|
||||
private String scopes;
|
||||
|
||||
private String userSessionTableName;
|
||||
private String redirectStateTableName;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// keep this secret, on the server - don't let it be serialized and sent to a client! //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
@JsonIgnore
|
||||
private String clientSecret;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Default Constructor.
|
||||
*******************************************************************************/
|
||||
public OAuth2AuthenticationMetaData()
|
||||
{
|
||||
super();
|
||||
setType(QAuthenticationType.OAUTH2);
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// ensure this module is registered with the dispatcher //
|
||||
//////////////////////////////////////////////////////////
|
||||
QAuthenticationModuleDispatcher.registerModule(QAuthenticationType.OAUTH2.getName(), OAuth2AuthenticationModule.class.getName());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public void validate(QInstance qInstance, QInstanceValidator qInstanceValidator)
|
||||
{
|
||||
super.validate(qInstance, qInstanceValidator);
|
||||
|
||||
String prefix = "OAuth2AuthenticationMetaData (named '" + getName() + "'): ";
|
||||
|
||||
qInstanceValidator.assertCondition(StringUtils.hasContent(baseUrl), prefix + "baseUrl must be set");
|
||||
qInstanceValidator.assertCondition(StringUtils.hasContent(clientId), prefix + "clientId must be set");
|
||||
qInstanceValidator.assertCondition(StringUtils.hasContent(clientSecret), prefix + "clientSecret must be set");
|
||||
qInstanceValidator.assertCondition(StringUtils.hasContent(scopes), prefix + "scopes must be set");
|
||||
|
||||
if(qInstanceValidator.assertCondition(StringUtils.hasContent(userSessionTableName), prefix + "userSessionTableName must be set"))
|
||||
{
|
||||
qInstanceValidator.assertCondition(qInstance.getTable(userSessionTableName) != null, prefix + "userSessionTableName ('" + userSessionTableName + "') was not found in the instance");
|
||||
}
|
||||
|
||||
if(qInstanceValidator.assertCondition(StringUtils.hasContent(redirectStateTableName), prefix + "redirectStateTableName must be set"))
|
||||
{
|
||||
qInstanceValidator.assertCondition(qInstance.getTable(redirectStateTableName) != null, prefix + "redirectStateTableName ('" + redirectStateTableName + "') was not found in the instance");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter, override to help fluent flows
|
||||
*******************************************************************************/
|
||||
public OAuth2AuthenticationMetaData withBaseUrl(String baseUrl)
|
||||
{
|
||||
setBaseUrl(baseUrl);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for baseUrl
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getBaseUrl()
|
||||
{
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for baseUrl
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setBaseUrl(String baseUrl)
|
||||
{
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter, override to help fluent flows
|
||||
*******************************************************************************/
|
||||
public OAuth2AuthenticationMetaData withClientId(String clientId)
|
||||
{
|
||||
setClientId(clientId);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for clientId
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getClientId()
|
||||
{
|
||||
return clientId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for clientId
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setClientId(String clientId)
|
||||
{
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter, override to help fluent flows
|
||||
*******************************************************************************/
|
||||
public OAuth2AuthenticationMetaData withClientSecret(String clientSecret)
|
||||
{
|
||||
setClientSecret(clientSecret);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for clientSecret
|
||||
**
|
||||
*******************************************************************************/
|
||||
public String getClientSecret()
|
||||
{
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for clientSecret
|
||||
**
|
||||
*******************************************************************************/
|
||||
public void setClientSecret(String clientSecret)
|
||||
{
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for tokenUrl
|
||||
*******************************************************************************/
|
||||
public String getTokenUrl()
|
||||
{
|
||||
return (this.tokenUrl);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for tokenUrl
|
||||
*******************************************************************************/
|
||||
public void setTokenUrl(String tokenUrl)
|
||||
{
|
||||
this.tokenUrl = tokenUrl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for tokenUrl
|
||||
*******************************************************************************/
|
||||
public OAuth2AuthenticationMetaData withTokenUrl(String tokenUrl)
|
||||
{
|
||||
this.tokenUrl = tokenUrl;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for userSessionTableName
|
||||
*******************************************************************************/
|
||||
public String getUserSessionTableName()
|
||||
{
|
||||
return (this.userSessionTableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for userSessionTableName
|
||||
*******************************************************************************/
|
||||
public void setUserSessionTableName(String userSessionTableName)
|
||||
{
|
||||
this.userSessionTableName = userSessionTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for userSessionTableName
|
||||
*******************************************************************************/
|
||||
public OAuth2AuthenticationMetaData withUserSessionTableName(String userSessionTableName)
|
||||
{
|
||||
this.userSessionTableName = userSessionTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for redirectStateTableName
|
||||
*******************************************************************************/
|
||||
public String getRedirectStateTableName()
|
||||
{
|
||||
return (this.redirectStateTableName);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for redirectStateTableName
|
||||
*******************************************************************************/
|
||||
public void setRedirectStateTableName(String redirectStateTableName)
|
||||
{
|
||||
this.redirectStateTableName = redirectStateTableName;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for redirectStateTableName
|
||||
*******************************************************************************/
|
||||
public OAuth2AuthenticationMetaData withRedirectStateTableName(String redirectStateTableName)
|
||||
{
|
||||
this.redirectStateTableName = redirectStateTableName;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for scopes
|
||||
*******************************************************************************/
|
||||
public String getScopes()
|
||||
{
|
||||
return (this.scopes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for scopes
|
||||
*******************************************************************************/
|
||||
public void setScopes(String scopes)
|
||||
{
|
||||
this.scopes = scopes;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for scopes
|
||||
*******************************************************************************/
|
||||
public OAuth2AuthenticationMetaData withScopes(String scopes)
|
||||
{
|
||||
this.scopes = scopes;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.authentication;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import com.fasterxml.jackson.annotation.JsonFilter;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
@ -225,4 +226,15 @@ public class QAuthenticationMetaData implements TopLevelMetaDataInterface
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public void validate(QInstance qInstance, QInstanceValidator qInstanceValidator)
|
||||
{
|
||||
//////////////////
|
||||
// noop at base //
|
||||
//////////////////
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,269 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.branding;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** element of BrandingMetaData - content to send to a frontend for showing a
|
||||
** user across the whole UI - e.g., what environment you're in, or a message
|
||||
** about your account - site announcements, etc.
|
||||
*******************************************************************************/
|
||||
public class Banner implements Serializable, Cloneable
|
||||
{
|
||||
private Severity severity;
|
||||
private String textColor;
|
||||
private String backgroundColor;
|
||||
private String messageText;
|
||||
private String messageHTML;
|
||||
|
||||
private Map<String, Serializable> additionalStyles;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public enum Severity
|
||||
{
|
||||
INFO, WARNING, ERROR, SUCCESS
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public Banner clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
Banner clone = (Banner) super.clone();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// copy mutable state here, so the clone can't change the internals of the original //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
if(additionalStyles != null)
|
||||
{
|
||||
clone.setAdditionalStyles(new LinkedHashMap<>(additionalStyles));
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for textColor
|
||||
*******************************************************************************/
|
||||
public String getTextColor()
|
||||
{
|
||||
return (this.textColor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for textColor
|
||||
*******************************************************************************/
|
||||
public void setTextColor(String textColor)
|
||||
{
|
||||
this.textColor = textColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for textColor
|
||||
*******************************************************************************/
|
||||
public Banner withTextColor(String textColor)
|
||||
{
|
||||
this.textColor = textColor;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for backgroundColor
|
||||
*******************************************************************************/
|
||||
public String getBackgroundColor()
|
||||
{
|
||||
return (this.backgroundColor);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for backgroundColor
|
||||
*******************************************************************************/
|
||||
public void setBackgroundColor(String backgroundColor)
|
||||
{
|
||||
this.backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for backgroundColor
|
||||
*******************************************************************************/
|
||||
public Banner withBackgroundColor(String backgroundColor)
|
||||
{
|
||||
this.backgroundColor = backgroundColor;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for additionalStyles
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getAdditionalStyles()
|
||||
{
|
||||
return (this.additionalStyles);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for additionalStyles
|
||||
*******************************************************************************/
|
||||
public void setAdditionalStyles(Map<String, Serializable> additionalStyles)
|
||||
{
|
||||
this.additionalStyles = additionalStyles;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for additionalStyles
|
||||
*******************************************************************************/
|
||||
public Banner withAdditionalStyles(Map<String, Serializable> additionalStyles)
|
||||
{
|
||||
this.additionalStyles = additionalStyles;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for messageText
|
||||
*******************************************************************************/
|
||||
public String getMessageText()
|
||||
{
|
||||
return (this.messageText);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for messageText
|
||||
*******************************************************************************/
|
||||
public void setMessageText(String messageText)
|
||||
{
|
||||
this.messageText = messageText;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for messageText
|
||||
*******************************************************************************/
|
||||
public Banner withMessageText(String messageText)
|
||||
{
|
||||
this.messageText = messageText;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for messageHTML
|
||||
*******************************************************************************/
|
||||
public String getMessageHTML()
|
||||
{
|
||||
return (this.messageHTML);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for messageHTML
|
||||
*******************************************************************************/
|
||||
public void setMessageHTML(String messageHTML)
|
||||
{
|
||||
this.messageHTML = messageHTML;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for messageHTML
|
||||
*******************************************************************************/
|
||||
public Banner withMessageHTML(String messageHTML)
|
||||
{
|
||||
this.messageHTML = messageHTML;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for severity
|
||||
*******************************************************************************/
|
||||
public Severity getSeverity()
|
||||
{
|
||||
return (this.severity);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for severity
|
||||
*******************************************************************************/
|
||||
public void setSeverity(Severity severity)
|
||||
{
|
||||
this.severity = severity;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for severity
|
||||
*******************************************************************************/
|
||||
public Banner withSeverity(Severity severity)
|
||||
{
|
||||
this.severity = severity;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.branding;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** interface to define keys for where banners should be displayed.
|
||||
** expect frontends to implement this interface with enums of known possible values
|
||||
*******************************************************************************/
|
||||
public interface BannerSlot
|
||||
{
|
||||
}
|
@ -22,6 +22,9 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.branding;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
|
||||
@ -30,7 +33,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.TopLevelMetaDataInterface;
|
||||
** Meta-Data to define branding in a QQQ instance.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
public class QBrandingMetaData implements TopLevelMetaDataInterface, Cloneable, Serializable
|
||||
{
|
||||
private String companyName;
|
||||
private String companyUrl;
|
||||
@ -39,9 +42,45 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
private String icon;
|
||||
private String accentColor;
|
||||
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
private String environmentBannerText;
|
||||
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
private String environmentBannerColor;
|
||||
|
||||
private Map<BannerSlot, Banner> banners;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QBrandingMetaData clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
QBrandingMetaData clone = (QBrandingMetaData) super.clone();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// copy mutable state here, so the clone can't change the internals of the original //
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
if(banners != null)
|
||||
{
|
||||
clone.banners = new LinkedHashMap<>();
|
||||
for(Map.Entry<BannerSlot, Banner> entry : this.banners.entrySet())
|
||||
{
|
||||
clone.banners.put(entry.getKey(), entry.getValue().clone());
|
||||
}
|
||||
}
|
||||
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -267,6 +306,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Getter for environmentBannerText
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public String getEnvironmentBannerText()
|
||||
{
|
||||
return (this.environmentBannerText);
|
||||
@ -277,6 +317,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Setter for environmentBannerText
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public void setEnvironmentBannerText(String environmentBannerText)
|
||||
{
|
||||
this.environmentBannerText = environmentBannerText;
|
||||
@ -287,6 +328,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for environmentBannerText
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public QBrandingMetaData withEnvironmentBannerText(String environmentBannerText)
|
||||
{
|
||||
this.environmentBannerText = environmentBannerText;
|
||||
@ -298,6 +340,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Getter for environmentBannerColor
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public String getEnvironmentBannerColor()
|
||||
{
|
||||
return (this.environmentBannerColor);
|
||||
@ -308,6 +351,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Setter for environmentBannerColor
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public void setEnvironmentBannerColor(String environmentBannerColor)
|
||||
{
|
||||
this.environmentBannerColor = environmentBannerColor;
|
||||
@ -318,6 +362,7 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
/*******************************************************************************
|
||||
** Fluent setter for environmentBannerColor
|
||||
*******************************************************************************/
|
||||
@Deprecated(since = "migrate to use banners map instead")
|
||||
public QBrandingMetaData withEnvironmentBannerColor(String environmentBannerColor)
|
||||
{
|
||||
this.environmentBannerColor = environmentBannerColor;
|
||||
@ -334,4 +379,52 @@ public class QBrandingMetaData implements TopLevelMetaDataInterface
|
||||
{
|
||||
qInstance.setBranding(this);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for banners
|
||||
*******************************************************************************/
|
||||
public Map<BannerSlot, Banner> getBanners()
|
||||
{
|
||||
return (this.banners);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for banners
|
||||
*******************************************************************************/
|
||||
public void setBanners(Map<BannerSlot, Banner> banners)
|
||||
{
|
||||
this.banners = banners;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Fluent setter for banners
|
||||
*******************************************************************************/
|
||||
public QBrandingMetaData withBanners(Map<BannerSlot, Banner> banners)
|
||||
{
|
||||
this.banners = banners;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public QBrandingMetaData withBanner(BannerSlot slot, Banner banner)
|
||||
{
|
||||
if(this.banners == null)
|
||||
{
|
||||
this.banners = new LinkedHashMap<>();
|
||||
}
|
||||
this.banners.put(slot, banner);
|
||||
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.code;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** an object which is intended to be constructed via a CodeReference, and,
|
||||
** moreso, after it is created, then the initialize method here gets called,
|
||||
** passing the codeRefernce in - e.g., to do additional initalization of the
|
||||
** object, e.g., properties in a QCodeReferenceWithProperties
|
||||
*******************************************************************************/
|
||||
public interface InitializableViaCodeReference
|
||||
{
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
void initialize(QCodeReference codeReference);
|
||||
|
||||
}
|
@ -23,13 +23,14 @@ package com.kingsrook.qqq.backend.core.model.metadata.code;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Pointer to code to be ran by the qqq framework, e.g., for custom behavior -
|
||||
** maybe process steps, maybe customization to a table, etc.
|
||||
*******************************************************************************/
|
||||
public class QCodeReference implements Serializable
|
||||
public class QCodeReference implements Serializable, Cloneable, QMetaDataObject
|
||||
{
|
||||
private String name;
|
||||
private QCodeType codeType;
|
||||
@ -58,6 +59,25 @@ public class QCodeReference implements Serializable
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QCodeReference clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
QCodeReference clone = (QCodeReference) super.clone();
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -179,5 +199,4 @@ public class QCodeReference implements Serializable
|
||||
this.inlineCode = inlineCode;
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2025. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.code;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** a code reference that also has a map of properties. This object (with the
|
||||
** properties) will be passed in to the referenced object, if it implements
|
||||
** InitializableViaCodeReference.
|
||||
*******************************************************************************/
|
||||
public class QCodeReferenceWithProperties extends QCodeReference
|
||||
{
|
||||
private final Map<String, Serializable> properties;
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
public QCodeReferenceWithProperties(Class<?> javaClass, Map<String, Serializable> properties)
|
||||
{
|
||||
super(javaClass);
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for properties
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Map<String, Serializable> getProperties()
|
||||
{
|
||||
return properties;
|
||||
}
|
||||
}
|
@ -62,7 +62,8 @@ public class WidgetAdHocValue extends AbstractWidgetValueSource
|
||||
context.putAll(inputValues);
|
||||
}
|
||||
|
||||
Function<Object, Object> function = QCodeLoader.getFunction(codeReference);
|
||||
@SuppressWarnings("unchecked")
|
||||
Function<Object, Object> function = QCodeLoader.getAdHoc(Function.class, codeReference);
|
||||
Object result = function.apply(context);
|
||||
return (result);
|
||||
}
|
||||
|
@ -40,11 +40,13 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ReflectiveBeanLikeClassUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
@ -54,7 +56,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
|
||||
** Meta-data to represent a single field in a table.
|
||||
**
|
||||
*******************************************************************************/
|
||||
public class QFieldMetaData implements Cloneable
|
||||
public class QFieldMetaData implements Cloneable, QMetaDataObject
|
||||
{
|
||||
private static final QLogger LOG = QLogger.getLogger(QFieldMetaData.class);
|
||||
|
||||
@ -187,7 +189,7 @@ public class QFieldMetaData implements Cloneable
|
||||
{
|
||||
try
|
||||
{
|
||||
this.name = QRecordEntity.getFieldNameFromGetter(getter);
|
||||
this.name = ReflectiveBeanLikeClassUtils.getFieldNameFromGetter(getter);
|
||||
this.type = QFieldType.fromClass(getter.getReturnType());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ -238,7 +240,7 @@ public class QFieldMetaData implements Cloneable
|
||||
|
||||
if(StringUtils.hasContent(fieldAnnotation.defaultValue()))
|
||||
{
|
||||
ValueUtils.getValueAsFieldType(this.type, fieldAnnotation.defaultValue());
|
||||
withDefaultValue(ValueUtils.getValueAsFieldType(this.type, fieldAnnotation.defaultValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2024. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Field behavior that changes the whitespace of string values.
|
||||
*******************************************************************************/
|
||||
public enum WhiteSpaceBehavior implements FieldBehavior<WhiteSpaceBehavior>, FieldBehaviorForFrontend, FieldFilterBehavior<WhiteSpaceBehavior>
|
||||
{
|
||||
NONE(null),
|
||||
REMOVE_ALL_WHITESPACE((String s) -> s.chars().filter(c -> !Character.isWhitespace(c)).collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()),
|
||||
TRIM((String s) -> s.trim()),
|
||||
TRIM_LEFT((String s) -> s.stripLeading()),
|
||||
TRIM_RIGHT((String s) -> s.stripTrailing());
|
||||
|
||||
|
||||
private final Function<String, String> function;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
WhiteSpaceBehavior(Function<String, String> function)
|
||||
{
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public WhiteSpaceBehavior getDefault()
|
||||
{
|
||||
return (NONE);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
if(this.equals(NONE))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch(this)
|
||||
{
|
||||
case REMOVE_ALL_WHITESPACE, TRIM, TRIM_LEFT, TRIM_RIGHT -> applyFunction(recordList, table, field);
|
||||
default -> throw new IllegalStateException("Unexpected enum value: " + this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private void applyFunction(List<QRecord> recordList, QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
String fieldName = field.getName();
|
||||
for(QRecord record : CollectionUtils.nonNullList(recordList))
|
||||
{
|
||||
String value = record.getValueString(fieldName);
|
||||
if(value != null && function != null)
|
||||
{
|
||||
record.setValue(fieldName, function.apply(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public Serializable applyToFilterCriteriaValue(Serializable value, QInstance instance, QTableMetaData table, QFieldMetaData field)
|
||||
{
|
||||
if(this.equals(NONE) || function == null)
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
|
||||
if(value instanceof String s)
|
||||
{
|
||||
String newValue = function.apply(s);
|
||||
if(!Objects.equals(value, newValue))
|
||||
{
|
||||
return (newValue);
|
||||
}
|
||||
}
|
||||
|
||||
return (value);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public boolean allowMultipleBehaviorsOfThisType()
|
||||
{
|
||||
return (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Override
|
||||
public List<String> validateBehaviorConfiguration(QTableMetaData tableMetaData, QFieldMetaData fieldMetaData)
|
||||
{
|
||||
if(this == NONE)
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> errors = new ArrayList<>();
|
||||
String errorSuffix = " field [" + fieldMetaData.getName() + "] in table [" + tableMetaData.getName() + "]";
|
||||
|
||||
if(fieldMetaData.getType() != null)
|
||||
{
|
||||
if(!fieldMetaData.getType().isStringLike())
|
||||
{
|
||||
errors.add("A WhiteSpaceBehavior was a applied to a non-String-like field:" + errorSuffix);
|
||||
}
|
||||
}
|
||||
|
||||
return (errors);
|
||||
}
|
||||
|
||||
}
|
@ -55,6 +55,7 @@ public class QFrontendFieldMetaData implements Serializable
|
||||
private String possibleValueSourceName;
|
||||
private String displayFormat;
|
||||
private Serializable defaultValue;
|
||||
private Integer maxLength;
|
||||
|
||||
private List<FieldAdornment> adornments;
|
||||
private List<QHelpContent> helpContents;
|
||||
@ -85,6 +86,7 @@ public class QFrontendFieldMetaData implements Serializable
|
||||
this.defaultValue = fieldMetaData.getDefaultValue();
|
||||
this.helpContents = fieldMetaData.getHelpContents();
|
||||
this.inlinePossibleValueSource = fieldMetaData.getInlinePossibleValueSource();
|
||||
this.maxLength = fieldMetaData.getMaxLength();
|
||||
|
||||
for(FieldBehavior<?> behavior : CollectionUtils.nonNullCollection(fieldMetaData.getBehaviors()))
|
||||
{
|
||||
|
@ -48,6 +48,8 @@ public class QFrontendProcessMetaData
|
||||
private String label;
|
||||
private String tableName;
|
||||
private boolean isHidden;
|
||||
private Integer minInputRecords;
|
||||
private Integer maxInputRecords;
|
||||
|
||||
private QIcon icon;
|
||||
|
||||
@ -72,6 +74,8 @@ public class QFrontendProcessMetaData
|
||||
this.tableName = processMetaData.getTableName();
|
||||
this.isHidden = processMetaData.getIsHidden();
|
||||
this.stepFlow = processMetaData.getStepFlow().toString();
|
||||
this.minInputRecords = processMetaData.getMinInputRecords();
|
||||
this.maxInputRecords = processMetaData.getMaxInputRecords();
|
||||
|
||||
if(includeSteps)
|
||||
{
|
||||
@ -213,4 +217,27 @@ public class QFrontendProcessMetaData
|
||||
{
|
||||
return icon;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for minInputRecords
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getMinInputRecords()
|
||||
{
|
||||
return minInputRecords;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for maxInputRecords
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Integer getMaxInputRecords()
|
||||
{
|
||||
return maxInputRecords;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.help;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -41,7 +42,7 @@ import java.util.Set;
|
||||
** May be dynamically added to meta-data via (non-meta-) data - see
|
||||
** HelpContentMetaDataProvider and QInstanceHelpContentManager
|
||||
*******************************************************************************/
|
||||
public class QHelpContent
|
||||
public class QHelpContent implements QMetaDataObject
|
||||
{
|
||||
private String content;
|
||||
private HelpFormat format;
|
||||
|
@ -22,11 +22,14 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Interface shared by meta-data objects which can be placed into an App.
|
||||
** e.g., Tables, Processes, and Apps themselves (since they can be nested)
|
||||
*******************************************************************************/
|
||||
public interface QAppChildMetaData
|
||||
public interface QAppChildMetaData extends QMetaDataObject
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
|
@ -24,12 +24,13 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** A section of apps/tables/processes - a logical grouping.
|
||||
*******************************************************************************/
|
||||
public class QAppSection implements Cloneable
|
||||
public class QAppSection implements Cloneable, QMetaDataObject
|
||||
{
|
||||
private String name;
|
||||
private String label;
|
||||
|
@ -22,6 +22,9 @@
|
||||
package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
|
||||
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.QMetaDataObject;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Icon to show associated with an App, Table, Process, etc.
|
||||
**
|
||||
@ -31,7 +34,7 @@ package com.kingsrook.qqq.backend.core.model.metadata.layout;
|
||||
** Future may allow something like a "namespace", and/or multiple icons for
|
||||
** use in different frontends, etc.
|
||||
*******************************************************************************/
|
||||
public class QIcon
|
||||
public class QIcon implements Cloneable, QMetaDataObject
|
||||
{
|
||||
private String name;
|
||||
private String path;
|
||||
@ -58,6 +61,25 @@ public class QIcon
|
||||
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
**
|
||||
***************************************************************************/
|
||||
@Override
|
||||
public QIcon clone()
|
||||
{
|
||||
try
|
||||
{
|
||||
QIcon clone = (QIcon) super.clone();
|
||||
return clone;
|
||||
}
|
||||
catch(CloneNotSupportedException e)
|
||||
{
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for name
|
||||
**
|
||||
@ -154,6 +176,4 @@ public class QIcon
|
||||
this.color = color;
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -216,11 +216,16 @@ public class SendSESAction
|
||||
{
|
||||
LOG.warn("More than one FROM value was found, will send using the first one found [" + partyList.get(0).getAddress() + "].");
|
||||
}
|
||||
Party fromParty = partyList.get(0);
|
||||
if(fromParty.getAddress() == null)
|
||||
{
|
||||
throw (new QException("Cannot send SES message because a FROM address was not provided."));
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// return the from address //
|
||||
/////////////////////////////
|
||||
return (partyList.get(0).getAddress());
|
||||
return (getFullEmailAddress(fromParty));
|
||||
}
|
||||
|
||||
|
||||
@ -267,15 +272,15 @@ public class SendSESAction
|
||||
{
|
||||
if(EmailPartyRole.CC.equals(party.getRole()))
|
||||
{
|
||||
ccList.add(party.getAddress());
|
||||
ccList.add(getFullEmailAddress(party));
|
||||
}
|
||||
else if(EmailPartyRole.BCC.equals(party.getRole()))
|
||||
{
|
||||
bccList.add(party.getAddress());
|
||||
bccList.add(getFullEmailAddress(party));
|
||||
}
|
||||
else if(party.getRole() == null || PartyRole.Default.DEFAULT.equals(party.getRole()) || EmailPartyRole.TO.equals(party.getRole()))
|
||||
{
|
||||
toList.add(party.getAddress());
|
||||
toList.add(getFullEmailAddress(party));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -332,4 +337,22 @@ public class SendSESAction
|
||||
|
||||
return amazonSES;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private String getFullEmailAddress(Party party)
|
||||
{
|
||||
if(party.getLabel() != null)
|
||||
{
|
||||
return (party.getLabel() + " <" + party.getAddress() + ">");
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// return the from address //
|
||||
/////////////////////////////
|
||||
return (party.getAddress());
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user