Compare commits

..

2 Commits

556 changed files with 5850 additions and 59790 deletions

View File

@ -1,51 +1,23 @@
#!/bin/bash
############################################################################
## adjust-pom.version.sh
## During CircleCI builds - edit the qqq parent pom.xml, to set the
## <revision> value such that:
## - feature-branch builds, tagged as snapshot-*, deploy with a version
## number that includes that tag's name (minus the snapshot- part)
## - integration-branch builds deploy with a version number that includes
## the branch name slugified
## - we never deploy -SNAPSHOT versions any more - because we don't believe
## it is ever valid to not know exactly what versions you are getting
## (perhaps because we are too loose with our versioning?)
############################################################################
if [ -z "$CIRCLE_BRANCH" ] && [ -z "$CIRCLE_TAG" ]; then
echo "Error: env vars CIRCLE_BRANCH and CIRCLE_TAG were not set."
exit 1;
fi
POM=$(dirname $0)/../pom.xml
echo "On branch: $CIRCLE_BRANCH, tag: $CIRCLE_TAG..."
######################################################################
## ## only do anything if the committed pom has a -SNAPSHOT version ##
######################################################################
REVISION=$(grep '<revision>' $POM | sed 's/.*<revision>//;s/<.*//');
echo "<revision> in pom.xml is: $REVISION"
if [ \! $(echo "$REVISION" | grep SNAPSHOT) ]; then
echo "Not on a SNAPSHOT revision, so nothing to do here."
if [ "$CIRCLE_BRANCH" == "dev" ] || [ "$CIRCLE_BRANCH" == "staging" ] || [ "$CIRCLE_BRANCH" == "main" ] || [ \! -z $(echo "$CIRCLE_TAG" | grep "^version-") ]; then
echo "On a primary branch or tag [${CIRCLE_BRANCH}${CIRCLE_TAG}] - will not edit the pom version.";
exit 0;
fi
##################################################################################
## ## figure out if we need a SLUG: a snapshot- tag, or an integration/ branch ##
##################################################################################
SLUG=""
if [ $(echo "$CIRCLE_TAG" | grep ^snapshot-) ]; then
SLUG=$(echo "$CIRCLE_TAG" | sed "s/^snapshot-//")-
echo "Using slug [$SLUG] from tag [$CIRCLE_TAG]"
elif [ $(echo "$CIRCLE_BRANCH" | grep ^integration/) ]; then
SLUG=$(echo "$CIRCLE_BRANCH" | sed "s,/,-,g")-
echo "Using slug [$SLUG] from branch [$CIRCLE_BRANCH]"
if [ -n "$CIRCLE_BRANCH" ]; then
SLUG=$(echo $CIRCLE_BRANCH | sed 's/[^a-zA-Z0-9]/-/g')
else
SLUG=$(echo $CIRCLE_TAG | sed 's/^snapshot-//g')
fi
################################################################
## ## build the replcaement for -SNAPSHOT, and update the pom ##
################################################################
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
REPLACEMENT=${SLUG}${TIMESTAMP}
POM=$(dirname $0)/../pom.xml
echo "Updating $POM -SNAPSHOT to: -$REPLACEMENT"
sed -i "s/-SNAPSHOT<\/revision>/-$REPLACEMENT<\/revision>/" $POM
echo "Updating $POM <revision> to: $SLUG-SNAPSHOT"
sed -i "s/<revision>.*/<revision>$SLUG-SNAPSHOT<\/revision>/" $POM
git diff $POM

View File

@ -2,7 +2,6 @@ version: 2.1
orbs:
localstack: localstack/platform@2.1
browser-tools: circleci/browser-tools@1.4.7
commands:
store_jacoco_site:
@ -39,8 +38,6 @@ commands:
- restore_cache:
keys:
- v1-dependencies-{{ checksum "pom.xml" }}
- browser-tools/install-chrome
- browser-tools/install-chromedriver
- run:
name: Write .env
command: |
@ -82,19 +79,6 @@ commands:
- ~/.m2
key: v1-dependencies-{{ checksum "pom.xml" }}
check_middleware_api_versions:
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "pom.xml" }}
- run:
name: Build and Run ValidateApiVersions
command: |
mvn -s .circleci/mvn-settings.xml -T4 install -DskipTests
mvn -s .circleci/mvn-settings.xml -pl qqq-middleware-javalin package appassembler:assemble -DskipTests
qqq-middleware-javalin/target/appassembler/bin/ValidateApiVersions -r $(pwd)
mvn_jar_deploy:
steps:
- checkout
@ -130,9 +114,14 @@ commands:
command: |
cd docs
asciidoctor -a docinfo=shared index.adoc
- store_artifacts:
path: docs/index.html
when: always
upload_docs_site:
steps:
- run:
name: scp html to justinsgotskinnylegs.com
command: |
cd docs
scp index.html dkelkhoff@45.79.44.221:/mnt/first-volume/dkelkhoff/nginx/html/justinsgotskinnylegs.com/qqq-docs.html
jobs:
mvn_test:
@ -141,7 +130,6 @@ jobs:
## - localstack/startup
- install_java17
- mvn_verify
- check_middleware_api_versions
mvn_deploy:
executor: localstack/default
@ -149,7 +137,6 @@ jobs:
## - localstack/startup
- install_java17
- mvn_verify
- check_middleware_api_versions
- mvn_jar_deploy
publish_asciidoc:
@ -157,6 +144,7 @@ jobs:
steps:
- install_asciidoctor
- run_asciidoctor
- upload_docs_site
workflows:
test_only:

View File

@ -136,13 +136,17 @@ This speaks to the fact that this "code" is not executable code - but rather is
**** The Filter button in the Query Screen will present a menu listing all fields from the table for the user to build ad-hoc queries against the table.
The data-types specified for the fields (in the meta-data) dictate what operators QQQ allows the user to use against fields (e.g., Strings offer "contains" vs Numbers offer "greater than").
**** Values for records from the table will be formatted for presentation based on the meta-data (such as a numeric field being shown with commas if it represents a quantity, or formatted as currency).
* Other kinds of information that you tell QQQ about in the form of meta-data objects includes:
** Details about the database you are using, and how to connect to it.
** A database table's name, fields, their types, its keys, and basic business rules (required fields, read-only fields, field lengths).
** The specification of a custom workflow (process), including what screens are needed, with input & output values, and references to the custom application code for processing the data.
** Details about a chart that summarizes data from a table for presentation as a dashboard widget.
** The description of web API - its URL and authentication mechanism.
** A table/path within a web API, and the fields returned in the JSON at that endpoint.
...
[start=2]
. *Meta Data* - declarative code - java object instances (potentially which could be read from `.yaml` files or other data sources in a future version of QQQ), which tell QQQ about the backend systems, tables, processes, reports, widgets, etc, that make up the application.
For example:
* Details about the database you are using, and how to connect to it.
* A database table's name, fields, their types, its keys, and basic business rules (required fields, read-only fields, field lengths).
* The description of web API - its URL and authentication mechanism.
* A table/path within a web API, and the fields returned in the JSON at that endpoint.
* The specification of a custom workflow (process), including what screens are needed, with input & output values, and references to the custom application code for processing the data.
* Details about a chart that summarizes data from a table for presentation as a dashboard widget.
// the section below is kinda dumb. like, it says you have to write application code, but
// then it just talks about how your app code gets for-free the same shit that QQQ does.
// it should instead say more about what your custom app code is or does.
@ -160,8 +164,7 @@ The data-types specified for the fields (in the meta-data) dictate what operator
// * The multi-threaded, paged producer/consumer pattern used in standard framework actions is how all custom application actions are also invoked.
// ** For example, the standard QQQ Bulk Edit action uses the same streamed-ETL process that custom application processes can use.
// Meaning your custom processes can take full advantage of the same complex frontend, middleware, and backend structural pieces, and you can just focus on your unique busines logic needs.
. *Application code* - to customize beyond what the QQQ framework does out-of-the box, and to provide application-specific business-logic.
2. *Application code* - to customize beyond what the QQQ framework does out-of-the box, and to provide application-specific business-logic.
QQQ provides its programmers the same classes that it internally uses for record access, resulting in a unified application model.
For example:

View File

@ -155,9 +155,9 @@ new QFilterOrderBy()
----
==== QueryJoin
* `joinTable` - *String, required (though inferrable)* - Name of the table that is being joined in to the existing query.
* `joinTable` - *String, required* - Name of the table that is being joined in to the existing query.
** Will be inferred from *joinMetaData*, if *joinTable* is not set when *joinMetaData* gets set.
* `baseTableOrAlias` - *String, required (though inferrable)* - Name of a table (or an alias) already defined in the query, to which the *joinTable* will be joined.
* `baseTableOrAlias` - *String, required* - Name of a table (or an alias) already defined in the query, to which the *joinTable* will be joined.
** Will be inferred from *joinMetaData*, if *baseTableOrAlias* is not set when *joinMetaData* gets set (which will only use the leftTableName from the joinMetaData - never an alias).
* `joinMetaData` - *QJoinMetaData object* - Optional specification of a {link-join} in the current QInstance.
If not set, will be looked up at runtime based on *baseTableOrAlias* and *joinTable*.
@ -165,78 +165,21 @@ If not set, will be looked up at runtime based on *baseTableOrAlias* and *joinTa
* `alias` - *String* - Optional (unless multiple instances of the same table are being joined together, when it becomes required).
Behavior based on SQL `FROM` clause aliases.
If given, must be used as the part before the dot in field name specifications throughout the rest of the query input.
* `select` - *boolean, default: false* - Specify whether fields from the *joinTable* should be selected by the query.
* `select` - *boolean, default: false* - Specify whether fields from the *rightTable* should be selected by the query.
If *true*, then the `QRecord` objects returned by this query will have values with corresponding to the (table-or-alias `.` field-name) form.
* `type` - *Enum of INNER, LEFT, RIGHT, FULL, default: INNER* - specifies the SQL-style type of join being performed.
[source,java]
.Basic QueryJoin usage example:
.QueryJoin definition examples:
----
// selecting from an "orderLine" table, joined to its corresponding (parent) "order" table
// selecting from an "orderLine" table - then join to its corresponding "order" table
queryInput.withTableName("orderLine");
queryInput.withQueryJoin(new QueryJoin("order").withSelect(true));
...
queryOutput.getRecords().get(0).getValueBigDecimal("order.grandTotal");
----
[source,java]
."V" shaped query - selecting from one parent table, and two children joined to it:
----
// TODO this needs verified for accuracy, though is a reasonable starting point as-is
// selecting from an "order" table, and two children of it, orderLine and customer
queryInput.withTableName("order");
queryInput.withQueryJoin(new QueryJoin("orderLine").withSelect(true));
queryInput.withQueryJoin(new QueryJoin("customer").withSelect(true));
...
QRecord joinedRecord = queryOutput.getRecords().get(0);
joinedRecord.getValueString("orderNo");
joinedRecord.getValueString("orderLine.sku");
joinedRecord.getValueString("customer.firstName");
----
[source,java]
."Chain" shaped query - selecting from one parent table, a child table, and a grandchild:
----
// TODO this needs verified for accuracy, though is a reasonable starting point as-is
// selecting from an "order" table, with a "customer" child table, and an "address" sub-table
queryInput.withTableName("order");
queryInput.withQueryJoin(new QueryJoin("customer").withSelect(true));
queryInput.withQueryJoin(new QueryJoin("address").withSelect(true));
...
QRecord joinedRecord = queryOutput.getRecords().get(0);
joinedRecord.getValueString("orderNo");
joinedRecord.getValueString("customer.firstName");
joinedRecord.getValueString("address.street1");
----
[source,java]
.QueryJoin usage example where two tables have two different joins between them:
----
// TODO this needs verified for accuracy, though is a reasonable starting point as-is
// here there's a "fulfillmentPlan" table, which points at the order table (many-to-one,
// as an order's plan can change over time, and we keep old plans around).
// This join is named: fulfillmentPlanJoinOrder
//
// The other join is "order" pointing at its current "fulfillmentPlan"
// This join is named: orderJoinCurrentFulfillmentPlan
// to select an order along with its current fulfillment plan:
queryInput.withTableName("order");
queryInput.withQueryJoin(new QueryJoin(instance.getJoin("orderJoinCurrentFulfillmentPlan"))
.withSelect(true));
// to select an order, and all fulfillment plans for an order (1 or more records):
queryInput.withTableName("order");
queryInput.withQueryJoin(new QueryJoin(instance.getJoin("fulfillmentPlanJoinOrder"))
.withSelect(true));
----
[source,java]
.QueryJoin usage example for table with two joins to the same child table, selecting from both:
----
// given an "order" table with 2 foreign keys to a customer table (billToCustomerId and shipToCustomerId)
// Note, we must supply the JoinMetaData to the QueryJoin, to drive what fields to join on in each case.
// we must also define an alias for each of the QueryJoins
queryInput.withTableName("order");
queryInput.withQueryJoins(List.of(
new QueryJoin(instance.getJoin("orderJoinShipToCustomer")
@ -247,18 +190,11 @@ queryInput.withQueryJoins(List.of(
.withSelect(true))));
...
record.getValueString("billToCustomer.firstName")
+ " paid for an order, to be sent to "
+ " placed an order for "
+ record.getValueString("shipToCustomer.firstName")
----
[source,java]
.Implicit QueryJoin, where unambiguous and required by QQueryFilter
----
// TODO finish and verify
queryInput.withTableName("order");
----
=== QueryOutput
* `records` - *List of QRecord* - List of 0 or more records that match the query filter.
** _Note: If a *recordPipe* was supplied to the QueryInput, then calling `queryOutput.getRecords()` will result in an `IllegalStateException` being thrown - as the records were placed into the pipe as they were fetched, and cannot all be accessed as a single list._

File diff suppressed because it is too large Load Diff

View File

@ -6,10 +6,7 @@
include::Introduction.adoc[leveloffset=+1]
== Meta Data Production
include::metaData/MetaDataProduction.adoc[leveloffset=+1]
== Meta Data Types
== Meta Data
// Organizational units
include::metaData/QInstance.adoc[leveloffset=+1]
include::metaData/Backends.adoc[leveloffset=+1]

View File

@ -56,37 +56,6 @@ if the value in the field is longer than the `maxLength`, then one of the follow
----
===== ValueRangeBehavior
Used on Numeric fields. Specifies min and/or max allowed values for the field.
For each of min and max, the following attributes can be set:
* `minValue` / `maxValue` - the number that is the limit.
* `minAllowEqualTo` / `maxAllowEqualTo` - boolean (default true). Controls if < (>) or ≤ (≥).
* `minBehavior` / `maxBehavior` - enum of `ERROR` (default) or `CLIP`.
** If `ERROR`, then a value not within the range causes an error, and the value does not get stored.
** else if `CLIP`, then a value not within the range gets "clipped" to either be the min/max (if allowEqualTo),
or to the min/max plus/minus the clipAmount
* `minClipAmount` / `maxClipAmount` - Default 1. Used when behavior is `CLIP` (only applies when
not allowEqualTo).
[source,java]
.Examples of using ValueRangeBehavior
----
new QFieldMetaData("noOfShoes", QFieldType.INTEGER)
.withBehavior(new ValueRangeBehavior().withMinValue(0));
new QFieldMetaData("price", QFieldType.BIG_DECIMAL)
.withBehavior(new ValueRangeBehavior()
// set the min value to be >= 0, and an error if an input is < 0.
.withMinValue(BigDecimal.ZERO)
.withMinAllowEqualTo(true)
.withMinBehavior(ERROR)
// set the max value to be < 100 - but effectively, clip larger values to 99.99
// here we use the .withMax() method that takes 4 params vs. calling 4 .withMax*() methods.
.withMax(new BigDecimal("100.00"), false, CLIP, new BigDecimal("0.01"))
);
----
===== DynamicDefaultValueBehavior
Used to set a dynamic default value to a field when it is being inserted or updated.
For example, instead of having a hard-coded `defaultValue` specified in the field meta-data,

View File

@ -1,413 +0,0 @@
[#MetaDataProduction]
include::../variables.adoc[]
The first thing that an application built using QQQ needs to do is to define its meta data.
This basically means the construction of a `QInstance` object, which is populated with the
meta data objects defining the backend(s), tables, processes, possible-value sources, joins,
authentication provider, etc, that make your application.
There are various styles that can be used for how you define your meta data, and for the
most part they can be mixed and matched. They will be presented here based on the historical
evolution of how they were added to QQQ, where we generally believe that better techniques have
been added over time. So, you may wish to skip the earlier techniques, and jump straight to
the end of this section. However, it can always be instructive to learn about the past, so,
read at your own pace.
== Omni-Provider
At the most basic level, the way to populate a `QInstance` is the simple and direct approach of creating
one big class, possibly with just one big method, and just doing all the work directly inline there.
This may (clearly) violate several good engineering principles. However, it does have the benefit of
being simple - if all of your meta-data is defined in one place, it can be pretty simple to find where
that place is. So - especially in a small project, this technique may be worth continuing to consider.
Re: "doing all the work" as mentioned above - what work are we talking about? At a minimum, we need
to construct the following meta-data objects, and pass them into our `QInstance`:
* `QAuthenticationMetaData` - how (or if!) users will be authenticated to the application.
* `QBackendMeataData` - a backend data store.
* `QTableMetaData` - a table (within the backend).
* `QAppMetaData` - an organizational unit to present the other elements in a UI.
Here's what a single-method omni-provider could look like:
[source,java]
.About the simplest possible single-file meta-data provider
----
public QInstance defineQInstance()
{
QInstance qInstance = new QInstance();
qInstance.setAuthentication(new QAuthenticationMetaData()
.withName("anonymous")
.withType(QAuthenticationType.FULLY_ANONYMOUS));
qInstance.addBackend(new QBackendMetaData()
.withBackendType(MemoryBackendModule.class)
.withName("memoryBackend"));
qInstance.addTable(new QTableMetaData()
.withName("myTable")
.withPrimaryKeyField("id")
.withBackendName("memoryBackend")
.withField(new QFieldMetaData("id", QFieldType.INTEGER)));
qInstance.addApp(new QAppMetaData()
.withName("myApp")
.withSectionOfChildren(new QAppSection().withName("mySection"),
qInstance.getTable("myTable")))
return (qInstance);
}
----
== Multi-method Omni-Provider
The next evolution of meta-data production comes by just applying some basic better-engineering
principles, and splitting up from a single method that constructs all the things, to at least
using unique methods to construct each thing, then calling those methods to add their results
to the QInstance.
[source,java]
.Multi-method omni- meta-data provider
----
public QInstance defineQInstance()
{
QInstance qInstance = new QInstance();
qInstance.setAuthentication(defineAuthenticationMetaData());
qInstance.addBackend(defineBackendMetaData());
qInstance.addTable(defineMyTableMetaData());
qInstance.addApp(defineMyAppMetaData(qInstance));
return qInstance;
}
public QAuthenticationMetaData defineAuthenticationMetaData()
{
return new QAuthenticationMetaData()
.withName("anonymous")
.withType(QAuthenticationType.FULLY_ANONYMOUS);
}
public QBackendMetaData defineBackendMetaData()
{
return new QBackendMetaData()
.withBackendType(MemoryBackendModule.class)
.withName("memoryBackend");
}
// implementations of defineMyTableMetaData() and defineMyAppMetaData(qInstance)
// left as an exercise for the reader
----
== Multi-class Providers
Then the next logical evolution would be to put each of these single meta-data producing
objects into its own class, along with calls to those classes. This gets us away from the
"5000 line" single-class, and lets us stop using the word "omni":
[source,java]
.Multi-class meta-data providers
----
public QInstance defineQInstance()
{
QInstance qInstance = new QInstance();
qInstance.setAuthentication(new AuthMetaDataProvider().defineAuthenticationMetaData());
qInstance.addBackend(new BackendMetaDataProvider().defineBackendMetaData());
qInstance.addTable(new MyTableMetaDataProvider().defineTableMetaData());
qInstance.addApp(new MyAppMetaDataProvider().defineAppMetaData(qInstance));
return qInstance;
}
public class AuthMetaDataProvider
{
public QAuthenticationMetaData defineAuthenticationMetaData()
{
return new QAuthenticationMetaData()
.withName("anonymous")
.withType(QAuthenticationType.FULLY_ANONYMOUS);
}
}
public class BackendMetaDataProvider
{
public QBackendMetaData defineBackendMetaData()
{
return new QBackendMetaData()
.withBackendType(MemoryBackendModule.class)
.withName("memoryBackend");
}
}
// implementations of MyTableMetaDataProvider and MyAppMetaDataProvider
// left as an exercise for the reader
----
== MetaDataProducerInterface
As the size of your application grows, if you're doing per-object meta-data providers, you may find it
burdensome, when adding a new object to your instance, to have to write code for it in two places -
that is - a new class to produce that meta-data object, AND a single line of code to add that object
to your `QInstance`. As such, a mechanism exists to let you avoid that line-of-code for adding the object
to the `QInstance`.
This mechanism involves adding the `MetaDataProducerInterface` to all of your classes that produce a
meta-data object. This interface is generic, with a type parameter that will typically be the type of
meta-data object you are producing, such as `QTableMetaData`, `QProcessMetaData`, or `QWidgetMetaData`,
(technically, any class which implements `TopLevelMetaData`). Implementers of the interface are then
required to override just one method: `T produce(QInstance qInstance) throws QException;`
Once you have your `MetaDataProducerInterface` classes defined, then there's a one-time call needed
to add all of the objects produced by these classes to your `QInstance` - as shown here:
[source,java]
.Using MetaDataProducerInterface
----
public QInstance defineQInstance()
{
QInstance qInstance = new QInstance();
MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance,
"com.mydomain.myapplication");
return qInstance;
}
public class AuthMetaDataProducer implements MetaDataProducerInterface<QAuthenticationMetaData>
{
@Override
public QAuthenticationMetaData produce(QInstance qInstance)
{
return new QAuthenticationMetaData()
.withName("anonymous")
.withType(QAuthenticationType.FULLY_ANONYMOUS);
}
}
public class BackendMetaDataProducer implements MetaDataProducerInterface<QBackendMetaData>
{
@Override
public QBackendMetaData defineBackendMetaData()
{
return new QBackendMetaData()
.withBackendType(MemoryBackendModule.class)
.withName("memoryBackend");
}
}
// implementations of MyTableMetaDataProvider and MyAppMetaDataProvider
// left as an exercise for the reader
----
=== MetaDataProducerMultiOutput
It is worth mentioning, that sometimes it might feel like a bridge that's a bit too far, to make
every single one of your meta-data objects require its own class. Some may argue that it's best
to do it that way - single responsibility principle, etc. But, if you're producing, say, 5 widgets
that are all related, and it's only a handful of lines of code for each one, maybe you'd rather
produce them all in the same class. Or maybe when you define a table, you'd like to define its
joins and widgets at the same time.
This approach can be accomplished by making the type argument for your `MetaDataProducerInterface` be
`MetaDataProducerMultiOutput` - a simple class that just wraps a list of other `MetaDataProducerOutput`
objects.
[source,java]
.Returning a MetaDataProducerMultiOutput
----
public class MyMultiProducer implements MetaDataProducerInterface<MetaDataProducerMultiOutput>
{
@Override
public MetaDataProducerMultiOutput produce(QInstance qInstance)
{
MetaDataProducerMultiOutput output = new MetaDataProducerMultiOutput();
output.add(new QPossibleValueSource()...);
output.add(new QJoinMetaData()...);
output.add(new QJoinMetaData()...);
output.add(new QWidgetMetaData()...);
output.add(new QTableMetaData()...);
return (output);
}
}
----
== Aside: TableMetaData with RecordEntities
At this point, let's take a brief aside to dig deeper into the creation of a `QTableMeta` object.
Tables, being probably the most important meta-data type in QQQ, have a lot of information that can
be specified in their meta-data object.
At the same time, if you're writing any custom code in your QQQ application
(e.g., any processes or table customizers), where you're working with records from tables, you may
prefer being able to work with entity beans (e.g., java classes with typed getter & setter methods),
rather than the default object type that QQQ's ORM actions return, the `QRecord`, which carries all
of its values in a `Map` (where you don't get compile-time checks of field names or data types).
QQQ has a mechanism for dealing with this - in the form of the `QRecordEntity` class.
So - if you want to build your application using entity beans (which is recommended, for the compile-time
safety that they provide in custom code), you will be writing a `QRecordEntity` class for each of your tables,
which will look like:
[source,java]
.QRecordEntity example
----
public class MyTable extends QRecordEntity
{
public static final String TABLE_NAME = "myTable";
@QField(isEditable = false, isPrimaryKey = true)
private Integer id;
@QField()
private String name;
// no-arg constructor and constructor that takes a QRecord
// getters & setters (and optional fluent setters)
}
----
The point of introducing this topic here and now is, that a `QRecordEntity` can be used to shortcut to
defining some of the attributes in a `QTableMetaData` object. Specifically, in a `MetaDataProducer<QTableMetaData>`
you may say:
[source,java]
.QTableMetaDataProducer using a QRecordEntity
----
public QTableMetaData produce(QInstance qInstance) throws QExcpetion
{
return new QTableMetaData()
.withName(MyTable.TABLE_NAME)
.withFieldsFromEntity(MyTable.class)
.withBackendName("memoryBackend");
}
----
That `withFieldsFromEntity` call is one of the biggest benefits of this technique. It allows you to avoid defining
all of the fields in you table in two places (the entity and the table meta-data).
== MetaData Producing Annotations for Entities
If you are using `QRecordEntity` classes that correspond to your tables, then you can take advantage of some
additional annotations on those classes, to produce more related meta-data objects associated with those tables.
The point of this is to eliminate boilerplate, and simplify / speed up the process of getting a new table
built and deployed in your application.
Furthermore, the case can be made that it is beneficial to keep the meta-data definition for a table as close
as possible to the entity that corresponds to the table. This enables modifications to the table (e.g., adding
a new field/column) to only require edits in one java source file, rather than necessarily requiring edits
in two files.
=== @QMetaDataProducingEntity
This is an annotation meant to be placed on a `QRecordEntity` subclass, which you would like to be
processed by an invocation of `MetaDataProducerHelper`, to automatically produce some meta-data
objects.
This annotation supports:
* Creating table meta-data for the corresponding record entity table. Enabled by setting `produceTableMetaData=true`.
** One may customize the table meta data that is produced automatically by supplying a class that extends
`MetaDataCustomizerInterface` in the annotation attribute `tableMetaDataCustomizer`.
** In addition to (or as an alternative to) the per-table `MetaDataCustomizerInterface` that can be specified
in `@QMetaDataProducingEntity.tableMetaDataCustomzier`, when an application calls
`MetaDataProducerHelper.processAllMetaDataProducersInPackage`, an additional `MetaDataCustomizerInterface` can be
given, to apply a common set of adjustments to all tales being generated by the call.
* Making a possible-value-source out of the table. Enabled by setting `producePossibleValueSource=true`.
* Processing child tables to create joins and childRecordList widgets
=== @ChildTable
This is an annotation used as a value that goes inside a `@QMetadataProducingEntity` annotation, to define
child-tables, e.g., for producing joins and childRecordList widgets related to the table defined in the entity class.
==== @ChildJoin
This is an annotation used as a value inside a `@ChildTable` inside a `@QMetadataProducingEntity` annotation,
to control the generation of a `QJoinMetaData`, as a `ONE_TO_MANY` type join from the table represented by
the annotated entity, to the table referenced in the `@ChildTable` annotation.
==== @ChildRecordListWidget
This is an annotation used as a value that goes inside a `@QMetadataProducingEntity` annotation, to control
the generation of a QWidgetMetaData - for a ChildRecordList widget.
[source,java]
.QRecordEntity with meta-data producing annotations and a table MetaDataCustomizer
----
@QMetaDataProducingEntity(
produceTableMetaData = true,
tableMetaDataCustomizer = MyTable.TableMetaDataCustomizer.class,
producePossibleValueSource = true,
childTables = {
@ChildTable(
childTableEntityClass = MyChildTable.class,
childJoin = @ChildJoin(enabled = true),
childRecordListWidget = @ChildRecordListWidget(enabled = true, label = "Children"))
}
)
public class MyTable extends QRecordEntity
{
public static final String TABLE_NAME = "myTable";
public static class TableMetaDataCustomizer implements MetaDataCustomizerInterface<QTableMetaData>
{
@Override
public QTableMetaData customizeMetaData(QInstance qInstance, QTableMetaData table) throws QException
{
String childJoinName = QJoinMetaData.makeInferredJoinName(TABLE_NAME, MyChildTable.TABLE_NAME);
table
.withUniqueKey(new UniqueKey("name"))
.withIcon(new QIcon().withName("table_bar"))
.withRecordLabelFormat("%s")
.withRecordLabelFields("name")
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1,
List.of("id", "name")))
// todo additional sections for other fields
.withSection(new QFieldSection("children", new QIcon().withName("account_tree"), Tier.T2)
.withWidgetName(childJoinName))
.withExposedJoin(new ExposedJoin()
.withLabel("Children")
.withJoinPath(List.of(childJoinName))
.withJoinTable(MyChildTable.TABLE_NAME));
return (table);
}
}
@QField(isEditable = false, isPrimaryKey = true)
private Integer id;
// remaining fields, constructors, getters & setters left as an exercise for the reader and/or the IDE
}
----
The class given in the example above, if processed by the `MetaDataProducerHelper`, would add the following
meta-data objects to your `QInstance`:
* A `QTableMetaData` named `myTable`, with all fields annotated as `@QField` from the `QRecordEntity` class,
and with additional attributes as set in the `TableMetaDataCustomizer` inner class.
* A `QPossibleValueSource` named `myTable`, of type `TABLE`, with `myTable` as its backing table.
* A `QJoinMetaData` named `myTableJoinMyChildTable`, as a `ONE_TO_MANY` type, between those two tables.
* A `QWidgetMetaData` named `myTableJoinMyChildTable`, as a `CHILD_RECORD_LIST` type, that will show a list of
records from `myChildTable` as a widget, when viewing a record from `myTable`.
== Other MetaData Producing Annotations
Similar to these annotations for a `RecordEntity`, a similar one exists for a `PossibleValueEnum` class,
to automatically write the meta-data to use that enum as a possible value source in your application:
=== @QMetaDataProducingPossibleValueEnum
This is an annotation to go on a `PossibleValueEnum` class, which you would like to be
processed by MetaDataProducerHelper, to automatically produce a PossibleValueSource meta-data
based on the enum.
[source,java]
.PossibleValueEnum with meta-data producing annotation
----
@QMetaDataProducingPossibleValueEnum(producePossibleValueSource = true)
public enum MyOptionsEnum implements PossibleValueEnum<Integer>
{
// values and methods left as exercise for reader
}
----
The enum given in the example above, if processed by the `MetaDataProducerHelper`, would add the following
meta-data object to your `QInstance`:
* A `QPossibleValueSource` named `MyOptionsEnum`, of type `ENUM`, with `MyOptionsEnum` as its backing enum.

View File

@ -38,13 +38,6 @@ See {link-permissionRules} for details.
*** 1) by a single call to `.withStepList(List<QStepMetaData>)`, which internally adds each step into the `steps` map.
*** 2) by multiple calls to `.addStep(QStepMetaData)`, which adds a step to both the `stepList` and `steps` map.
** If a process also needs optional steps (for a <<_custom_process_flow>>), they should be added by a call to `.addOptionalStep(QStepMetaData)`, which only places them in the `steps` map.
* `stepFlow` - *enum, default LINEAR* - specifies the the flow-control logic between steps. Possible values are:
** `LINEAR` - steps are executed in-order, through the `stepList`.
A backend step _can_ customize the `nextStepName` or re-order the `stepList`, if needed.
In a frontend step, a user may be given the option to go _back_ to a previous step as well.
** `STATE_MACHINE` - steps are executed as a Fine State Machine, starting with the first step in `stepList`,
but then proceeding based on the `nextStepName` specified by the previous step.
Thus allowing much more flexible flows.
* `schedule` - *<<QScheduleMetaData>>* - set up the process to run automatically on the specified schedule.
See below for details.
* `minInputRecords` - *Integer* - #not used...#
@ -74,11 +67,6 @@ For processes with a user-interface, they must define one or more "screens" in t
* `formFields` - *List of String* - list of field names used by the screen as form-inputs.
* `viewFields` - *List of String* - list of field names used by the screen as visible outputs.
* `recordListFields` - *List of String* - list of field names used by the screen in a record listing.
* `format` - *Optional String* - directive for a frontend to use specialized formatting for the display of the process.
** Consult frontend documentation for supported values and their meanings.
* `backStepName` - *Optional String* - For processes using `LINEAR` flow, if this value is given,
then the frontend should offer a control that the user can take (e.g., a button) to move back to an
earlier step in the process.
==== QFrontendComponentMetaData
@ -102,13 +90,10 @@ Expects a process value named `html`.
Expects process values named `downloadFileName` and `serverFilePath`.
** `GOOGLE_DRIVE_SELECT_FOLDER` - Special form that presents a UI from Google Drive, where the user can select a folder (e.g., as a target for uploading files in a subsequent backend step).
** `BULK_EDIT_FORM` - For use by the standard QQQ Bulk Edit process.
** `BULK_LOAD_FILE_MAPPING_FORM`, `BULK_LOAD_VALUE_MAPPING_FORM`, or `BULK_LOAD_PROFILE_FORM` - For use by the standard QQQ Bulk Load process.
** `VALIDATION_REVIEW_SCREEN` - For use by the QQQ Streamed ETL With Frontend process family of processes.
Displays a component prompting the user to run full validation or to skip it, or, if full validation has been ran, then showing the results of that validation.
** `PROCESS_SUMMARY_RESULTS` - For use by the QQQ Streamed ETL With Frontend process family of processes.
Displays the summary results of running the process.
** `WIDGET` - Render a QQQ Widget.
Requires that `widgetName` be given as a value for the component.
** `RECORD_LIST` - _Deprecated.
Showed a grid with a list of records as populated by the process._
* `values` - *Map of String → Serializable* - Key=value pairs, with different expectations based on the component's `type`.
@ -131,27 +116,6 @@ It can be used, however, for example, to cause a `defaultValue` to be applied to
It can also be used to cause the process to throw an error, if a field is marked as `isRequired`, but a value is not present.
** `recordListMetaData` - *RecordListMetaData object* - _Not used at this time._
==== QStateMachineStep
Processes that use `flow = STATE_MACHINE` should use process steps of type `QStateMachineStep`.
A common pattern seen in state-machine processes, is that they will present a frontend-step to a user,
then always run a given backend-step in response to that screen which the user submitted.
Inside that backend-step, custom application logic will determine the next state to go to,
which is typically another frontend-step (which would then submit data to its corresponding backend-step,
and continue the FSM).
To help facilitate this pattern, factory methods exist on `QStateMachineStep`,
for constructing the commonly-expected types of state-machine steps:
* `frontendThenBackend(name, frontendStep, backendStep)` - for the frontend-then-backend pattern described above.
* `backendOnly(name, backendStep)` - for a state that only has a backend step.
This might be useful as a “reset” step, to run before restarting a state-loop.
* `frontendOnly(name, frontendStep)` - for a state that only has a frontend step,
which would always be followed by another state, which must be specified as the `defaultNextStepName`
on the `QStateMachineStep`.
==== BasepullConfiguration
A "Basepull" process is a common pattern where an application needs to perform some action on all new (or updated) records from a particular data source.
@ -254,10 +218,12 @@ But for some cases, doing page-level transactions can reduce long-transactions a
* `withSchedule(QScheduleMetaData schedule)` - Add a <<QScheduleMetaData>> to the process.
[#_custom_process_flow]
==== How to customize a Linear process flow
As referenced in the definition of the <<_QProcessMetaData_Properties,QProcessMetaData Properties>>, by default,
(with `flow = LINEAR`) a process will execute each of its steps in-order, as defined in the `stepList` property.
However, a Backend Step can customize this flow as follows:
==== Custom Process Flow
As referenced in the definition of the <<_QProcessMetaData_Properties,QProcessMetaData Properties>>, by default, a process
will execute each of its steps in-order, as defined in the `stepList` property.
However, a Backend Step can customize this flow #todo - write more clearly here...
There are generally 2 method to call (in a `BackendStep`) to do a dynamic flow:
* `RunBackendStepOutput.setOverrideLastStepName(String stepName)`
** QQQ's `RunProcessAction` keeps track of which step it "last" ran, e.g., to tell it which one to run next.
@ -273,7 +239,7 @@ does need to be found in the new `stepNameList` - otherwise, the framework will
for figuring out where to go next.
[source,java]
.Example of a defining process that can use a customized linear flow:
.Example of a defining process that can use a flexible flow:
----
// for a case like this, it would be recommended to define all step names in constants:
public final static String STEP_START = "start";
@ -358,21 +324,4 @@ public static class StartStep implements BackendStep
}
----
[#_process_back]
==== How to allow a process to go back
The simplest option to allow a process to present a "Back" button to users,
thus allowing them to move backward through a process
(e.g., from a review screen back to an earlier input screen), is to set the property `backStepName`
on a `QFrontendStepMetaData`.
If the step that is executed after the user hits "Back" is a backend step, then within that
step, `runBackendStepInput.getIsStepBack()` will return `true` (but ONLY within that first step after
the user hits "Back"). It may be necessary within individual processes to be aware that the user
has chosen to go back, to reset certain values in the process's state.
Alternatively, if a frontend step's "Back" behavior needs to be dynamic (e.g., sometimes not available,
or sometimes targeting different steps in the process), then in a backend step that runs before the
frontend step, a call to `runBackendStepOutput.getProcessState().setBackStepName()` can be made,
to customize the value which would otherwise come from the `QFrontendStepMetaData`.

View File

@ -29,21 +29,11 @@ service.routes(qJavalinImplementation.getRoutes());
service.start();
----
*QInstance Setup:*
*QBackendMetaData Setup Methods:*
These are the methods that one is most likely to use when setting up (defining) a `QInstance` object:
* `add(TopLevelMetaDataInterface metaData)` - Generic method that takes most of the meta-data subtypes that can be added
to an instance, such as `QBackendMetaData`, `QTableMetaData`, `QProcessMetaData`, etc.
There are also type-specific methods (e.g., `addTable`, `addProcess`, etc), which one can call instead - this would just
be a matter of personal preference.
* asdf
*QInstance Usage:*
*QBackendMetaData Usage Methods:*
Generally you will set up a `QInstance` in your application's startup flow, and then place it in the server (e.g., javalin).
But, if, during application-code runtime, you need access to any of the meta-data in the instance, you access it
via the `QContext` object's static `getInstance()` method. This can be useful, for example, to get a list of the defined
tables in the application, or fields in a table, or details about a field, etc.
It is generally considered risky and/or not a good idea at all to modify the `QInstance` after it has been validated and
a server is running. Future versions of QQQ may in fact restrict modifications to the instance after validation.

View File

@ -34,10 +34,8 @@
<module>qqq-backend-module-api</module>
<module>qqq-backend-module-filesystem</module>
<module>qqq-backend-module-rdbms</module>
<module>qqq-backend-module-sqlite</module>
<module>qqq-backend-module-mongodb</module>
<module>qqq-language-support-javascript</module>
<module>qqq-openapi</module>
<module>qqq-middleware-picocli</module>
<module>qqq-middleware-javalin</module>
<module>qqq-middleware-lambda</module>
@ -48,7 +46,7 @@
</modules>
<properties>
<revision>0.24.0-SNAPSHOT</revision>
<revision>0.23.0-SNAPSHOT</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

View File

@ -100,12 +100,7 @@
<dependency>
<groupId>org.dhatim</groupId>
<artifactId>fastexcel</artifactId>
<version>0.18.4</version>
</dependency>
<dependency>
<groupId>org.dhatim</groupId>
<artifactId>fastexcel-reader</artifactId>
<version>0.18.4</version>
<version>0.12.15</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
@ -117,14 +112,6 @@
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<!-- adding to help FastExcel -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.16.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>auth0</artifactId>

View File

@ -186,7 +186,7 @@ public class AsyncRecordPipeLoop
if(recordCount > 0)
{
LOG.debug("End of job summary", logPair("recordCount", recordCount), logPair("jobName", jobName), logPair("millis", endTime - jobStartTime), logPair("recordsPerSecond", 1000d * (recordCount / (.001d + (endTime - jobStartTime)))));
LOG.info("End of job summary", logPair("recordCount", recordCount), logPair("jobName", jobName), logPair("millis", endTime - jobStartTime), logPair("recordsPerSecond", 1000d * (recordCount / (.001d + (endTime - jobStartTime)))));
}
return (recordCount);

View File

@ -44,7 +44,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.security.MultiRecordSecurityLock;
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;
@ -174,49 +173,22 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
Map<String, Serializable> securityKeyValues = new HashMap<>();
for(RecordSecurityLock recordSecurityLock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(table.getRecordSecurityLocks())))
{
getRecordSecurityKeyValues(table, record, oldRecord, recordSecurityLock, securityKeyValues);
}
return securityKeyValues;
}
Serializable keyValue = record == null ? null : record.getValue(recordSecurityLock.getFieldName());
/***************************************************************************
** recursive implementation of getRecordSecurityKeyValues, for dealing with
** multi-locks
***************************************************************************/
private static void getRecordSecurityKeyValues(QTableMetaData table, QRecord record, Optional<QRecord> oldRecord, RecordSecurityLock recordSecurityLock, Map<String, Serializable> securityKeyValues)
{
//////////////////////////////////////////////////////
// special case with recursive call for multi-locks //
//////////////////////////////////////////////////////
if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock)
{
for(RecordSecurityLock subLock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(multiRecordSecurityLock.getLocks())))
if(keyValue == null && oldRecord.isPresent())
{
getRecordSecurityKeyValues(table, record, oldRecord, subLock, securityKeyValues);
LOG.debug("Table with a securityLock, but value not found in field", logPair("table", table.getName()), logPair("field", recordSecurityLock.getFieldName()));
keyValue = oldRecord.get().getValue(recordSecurityLock.getFieldName());
}
return;
if(keyValue == null)
{
LOG.debug("Table with a securityLock, but value not found in field", logPair("table", table.getName()), logPair("field", recordSecurityLock.getFieldName()), logPair("oldRecordIsPresent", oldRecord.isPresent()));
}
securityKeyValues.put(recordSecurityLock.getSecurityKeyType(), keyValue);
}
///////////////////////////////////////////
// by default, deal with non-multi locks //
///////////////////////////////////////////
Serializable keyValue = record == null ? null : record.getValue(recordSecurityLock.getFieldName());
if(keyValue == null && oldRecord.isPresent())
{
LOG.debug("Table with a securityLock, but value not found in field", logPair("table", table.getName()), logPair("field", recordSecurityLock.getFieldName()));
keyValue = oldRecord.get().getValue(recordSecurityLock.getFieldName());
}
if(keyValue == null)
{
LOG.debug("Table with a securityLock, but value not found in field", logPair("table", table.getName()), logPair("field", recordSecurityLock.getFieldName()), logPair("oldRecordIsPresent", oldRecord.isPresent()));
}
securityKeyValues.put(recordSecurityLock.getSecurityKeyType(), keyValue);
return securityKeyValues;
}
@ -246,16 +218,21 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
throw (new QException("Requested audit for an unrecognized table name: " + auditSingleInput.getAuditTableName()));
}
///////////////////////////////////////////////////////
// validate security keys on the table are given //
// originally, this case threw... //
// but i think it's better to record the audit, just //
// missing its security key value, then to fail... //
// but, maybe should be configurable, etc... //
///////////////////////////////////////////////////////
if(!validateSecurityKeys(auditSingleInput, table))
///////////////////////////////////////////////////
// validate security keys on the table are given //
///////////////////////////////////////////////////
for(RecordSecurityLock recordSecurityLock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(table.getRecordSecurityLocks())))
{
LOG.debug("Missing securityKeyValue in audit request", logPair("table", auditSingleInput.getAuditTableName()), logPair("auditMessage", auditSingleInput.getMessage()), logPair("recordId", auditSingleInput.getRecordId()));
if(auditSingleInput.getSecurityKeyValues() == null || !auditSingleInput.getSecurityKeyValues().containsKey(recordSecurityLock.getSecurityKeyType()))
{
///////////////////////////////////////////////////////
// originally, this case threw... //
// but i think it's better to record the audit, just //
// missing its security key value, then to fail... //
///////////////////////////////////////////////////////
// throw (new QException("Missing securityKeyValue [" + recordSecurityLock.getSecurityKeyType() + "] in audit request for table " + auditSingleInput.getAuditTableName()));
LOG.info("Missing securityKeyValue in audit request", logPair("table", auditSingleInput.getAuditTableName()), logPair("securityKey", recordSecurityLock.getSecurityKeyType()));
}
}
////////////////////////////////////////////////
@ -333,70 +310,6 @@ public class AuditAction extends AbstractQActionFunction<AuditInput, AuditOutput
/***************************************************************************
**
***************************************************************************/
static boolean validateSecurityKeys(AuditSingleInput auditSingleInput, QTableMetaData table)
{
boolean allAreValid = true;
for(RecordSecurityLock recordSecurityLock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(table.getRecordSecurityLocks())))
{
boolean lockIsValid = validateSecurityKeysForLock(auditSingleInput, recordSecurityLock);
if(!lockIsValid)
{
allAreValid = false;
}
}
return (allAreValid);
}
/***************************************************************************
**
***************************************************************************/
private static boolean validateSecurityKeysForLock(AuditSingleInput auditSingleInput, RecordSecurityLock recordSecurityLock)
{
if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock)
{
boolean allSubLocksAreValid = true;
boolean anySubLocksAreValid = false;
for(RecordSecurityLock lock : RecordSecurityLockFilters.filterForReadLocks(CollectionUtils.nonNullList(multiRecordSecurityLock.getLocks())))
{
boolean subLockIsValid = validateSecurityKeysForLock(auditSingleInput, lock);
if(subLockIsValid)
{
anySubLocksAreValid = true;
}
else
{
allSubLocksAreValid = false;
}
}
if(multiRecordSecurityLock.getOperator().equals(MultiRecordSecurityLock.BooleanOperator.OR))
{
return (anySubLocksAreValid);
}
else if(multiRecordSecurityLock.getOperator().equals(MultiRecordSecurityLock.BooleanOperator.AND))
{
return (allSubLocksAreValid);
}
}
else
{
if(auditSingleInput.getSecurityKeyValues() == null || !auditSingleInput.getSecurityKeyValues().containsKey(recordSecurityLock.getSecurityKeyType()))
{
return (false);
}
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -22,19 +22,15 @@
package com.kingsrook.qqq.backend.core.actions.customizers;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.actions.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.utils.CollectionUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -51,6 +47,7 @@ public interface TableCustomizerInterface
{
QLogger LOG = QLogger.getLogger(TableCustomizerInterface.class);
/*******************************************************************************
** custom actions to run after a query (or get!) takes place.
**
@ -80,15 +77,8 @@ public interface TableCustomizerInterface
*******************************************************************************/
default List<QRecord> preInsert(InsertInput insertInput, List<QRecord> records, boolean isPreview) throws QException
{
try
{
return (preInsertOrUpdate(insertInput, records, isPreview, Optional.empty()));
}
catch(NotImplementedHereException e)
{
LOG.info("A default implementation of preInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
return (records);
}
LOG.info("A default implementation of preInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
return (records);
}
@ -114,15 +104,8 @@ public interface TableCustomizerInterface
*******************************************************************************/
default List<QRecord> postInsert(InsertInput insertInput, List<QRecord> records) throws QException
{
try
{
return (postInsertOrUpdate(insertInput, records, Optional.empty()));
}
catch(NotImplementedHereException e)
{
LOG.info("A default implementation of postInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
return (records);
}
LOG.info("A default implementation of postInsert is running... Probably not expected!", logPair("tableName", insertInput.getTableName()));
return (records);
}
@ -147,15 +130,8 @@ public interface TableCustomizerInterface
*******************************************************************************/
default List<QRecord> preUpdate(UpdateInput updateInput, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{
try
{
return (preInsertOrUpdate(updateInput, records, isPreview, oldRecordList));
}
catch(NotImplementedHereException e)
{
LOG.info("A default implementation of preUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
return (records);
}
LOG.info("A default implementation of preUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
return (records);
}
@ -175,15 +151,8 @@ public interface TableCustomizerInterface
*******************************************************************************/
default List<QRecord> postUpdate(UpdateInput updateInput, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
try
{
return (postInsertOrUpdate(updateInput, records, oldRecordList));
}
catch(NotImplementedHereException e)
{
LOG.info("A default implementation of postUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
return (records);
}
LOG.info("A default implementation of postUpdate is running... Probably not expected!", logPair("tableName", updateInput.getTableName()));
return (records);
}
@ -230,59 +199,4 @@ public interface TableCustomizerInterface
return (records);
}
/***************************************************************************
** Optional method to override in a customizer that does the same thing for
** both preInsert & preUpdate.
***************************************************************************/
default List<QRecord> preInsertOrUpdate(AbstractActionInput input, List<QRecord> records, boolean isPreview, Optional<List<QRecord>> oldRecordList) throws QException
{
throw NotImplementedHereException.instance;
}
/***************************************************************************
** Optional method to override in a customizer that does the same thing for
** both postInsert & postUpdate.
***************************************************************************/
default List<QRecord> postInsertOrUpdate(AbstractActionInput input, List<QRecord> records, Optional<List<QRecord>> oldRecordList) throws QException
{
throw NotImplementedHereException.instance;
}
/***************************************************************************
**
***************************************************************************/
default Optional<Map<Serializable, QRecord>> oldRecordListToMap(String primaryKeyField, Optional<List<QRecord>> oldRecordList)
{
if(oldRecordList.isPresent())
{
return (Optional.of(CollectionUtils.listToMap(oldRecordList.get(), r -> r.getValue(primaryKeyField))));
}
else
{
return (Optional.empty());
}
}
/***************************************************************************
**
***************************************************************************/
class NotImplementedHereException extends QException
{
private static NotImplementedHereException instance = new NotImplementedHereException();
/***************************************************************************
**
***************************************************************************/
private NotImplementedHereException()
{
super("Not implemented here");
}
}
}

View File

@ -37,8 +37,6 @@ import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QNotFoundException;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.count.CountInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
@ -53,15 +51,12 @@ import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.ChildRecordListData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.AbstractWidgetMetaDataBuilder;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -88,9 +83,7 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
.withIsCard(true)
.withCodeReference(new QCodeReference(ChildRecordListRenderer.class))
.withType(WidgetType.CHILD_RECORD_LIST.getType())
.withDefaultValue("joinName", join.getName())
.withValidatorPlugin(new ChildRecordListWidgetValidator())
));
.withDefaultValue("joinName", join.getName())));
}
@ -175,7 +168,6 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
widgetMetaData.withDefaultValue("manageAssociationName", manageAssociationName);
return (this);
}
}
@ -202,7 +194,7 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
}
else if(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"))
{
maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().get("maxRows"));
maxRows = ValueUtils.getValueAsInteger(input.getWidgetMetaData().getDefaultValues().containsKey("maxRows"));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -307,18 +299,8 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
}
}
}
if(widgetValues.containsKey("defaultValuesForNewChildRecordsFromParentFields"))
{
@SuppressWarnings("unchecked")
Map<String, String> defaultValuesForNewChildRecordsFromParentFields = (Map<String, String>) widgetValues.get("defaultValuesForNewChildRecordsFromParentFields");
widgetData.setDefaultValuesForNewChildRecordsFromParentFields(defaultValuesForNewChildRecordsFromParentFields);
}
}
widgetData.setAllowRecordEdit(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("allowRecordEdit"))));
widgetData.setAllowRecordDelete(BooleanUtils.isTrue(ValueUtils.getValueAsBoolean(input.getQueryParams().get("allowRecordDelete"))));
return (new RenderWidgetOutput(widgetData));
}
catch(Exception e)
@ -328,68 +310,4 @@ public class ChildRecordListRenderer extends AbstractWidgetRenderer
}
}
/***************************************************************************
**
***************************************************************************/
private static class ChildRecordListWidgetValidator implements QInstanceValidatorPluginInterface<QWidgetMetaDataInterface>
{
/***************************************************************************
**
***************************************************************************/
@Override
public void validate(QWidgetMetaDataInterface widgetMetaData, QInstance qInstance, QInstanceValidator qInstanceValidator)
{
String prefix = "Widget " + widgetMetaData.getName() + ": ";
//////////////////////////////////
// make sure join name is given //
//////////////////////////////////
String joinName = ValueUtils.getValueAsString(CollectionUtils.nonNullMap(widgetMetaData.getDefaultValues()).get("joinName"));
if(qInstanceValidator.assertCondition(StringUtils.hasContent(joinName), prefix + "defaultValue for joinName must be given"))
{
///////////////////////////
// make sure join exists //
///////////////////////////
QJoinMetaData join = qInstance.getJoin(joinName);
if(qInstanceValidator.assertCondition(join != null, prefix + "No join named " + joinName + " exists in the instance"))
{
//////////////////////////////////////////////////////////////////////////////////
// if there's a manageAssociationName, make sure the table has that association //
//////////////////////////////////////////////////////////////////////////////////
String manageAssociationName = ValueUtils.getValueAsString(widgetMetaData.getDefaultValues().get("manageAssociationName"));
if(StringUtils.hasContent(manageAssociationName))
{
validateAssociationName(prefix, manageAssociationName, join, qInstance, qInstanceValidator);
}
}
}
}
/***************************************************************************
**
***************************************************************************/
private void validateAssociationName(String prefix, String manageAssociationName, QJoinMetaData join, QInstance qInstance, QInstanceValidator qInstanceValidator)
{
///////////////////////////////////
// make sure join's table exists //
///////////////////////////////////
QTableMetaData table = qInstance.getTable(join.getLeftTable());
if(table == null)
{
qInstanceValidator.getErrors().add(prefix + "Unable to validate manageAssociationName, as table [" + join.getLeftTable() + "] on left-side table of join [" + join.getName() + "] does not exist.");
}
else
{
if(CollectionUtils.nonNullList(table.getAssociations()).stream().noneMatch(a -> manageAssociationName.equals(a.getName())))
{
qInstanceValidator.getErrors().add(prefix + "an association named [" + manageAssociationName + "] does not exist on table [" + join.getLeftTable() + "]");
}
}
}
}
}

View File

@ -23,11 +23,11 @@ package com.kingsrook.qqq.backend.core.actions.dashboard.widgets;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput;
import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetOutput;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.AlertData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
@ -40,9 +40,9 @@ import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
** - alertType - name of entry in AlertType enum (ERROR, WARNING, SUCCESS)
** - alertHtml - html to display inside the alert (other than its icon)
*******************************************************************************/
public class AlertWidgetRenderer extends AbstractWidgetRenderer implements MetaDataProducerInterface<QWidgetMetaData>
public class ProcessAlertWidget extends AbstractWidgetRenderer implements MetaDataProducerInterface<QWidgetMetaData>
{
public static final String NAME = "AlertWidgetRenderer";
public static final String NAME = "ProcessAlertWidget";

View File

@ -1,92 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.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 allows all the things
*******************************************************************************/
public class AllowAllMetaDataFilter implements MetaDataFilterInterface
{
/***************************************************************************
**
***************************************************************************/
@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);
}
}

View File

@ -28,18 +28,13 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
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;
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.logging.QLogger;
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.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendAppMetaData;
@ -54,7 +49,6 @@ 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;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
/*******************************************************************************
@ -63,12 +57,6 @@ import com.kingsrook.qqq.backend.core.utils.memoization.Memoization;
*******************************************************************************/
public class MetaDataAction
{
private static final QLogger LOG = QLogger.getLogger(MetaDataAction.class);
private static Memoization<QInstance, MetaDataFilterInterface> metaDataFilterMemoization = new Memoization<>();
/*******************************************************************************
**
*******************************************************************************/
@ -76,10 +64,10 @@ public class MetaDataAction
{
ActionHelper.validateSession(metaDataInput);
MetaDataOutput metaDataOutput = new MetaDataOutput();
Map<String, AppTreeNode> treeNodes = new LinkedHashMap<>();
// todo pre-customization - just get to modify the request?
MetaDataOutput metaDataOutput = new MetaDataOutput();
MetaDataFilterInterface filter = getMetaDataFilter();
Map<String, AppTreeNode> treeNodes = new LinkedHashMap<>();
/////////////////////////////////////
// map tables to frontend metadata //
@ -90,11 +78,6 @@ public class MetaDataAction
String tableName = entry.getKey();
QTableMetaData table = entry.getValue();
if(!filter.allowTable(metaDataInput, table))
{
continue;
}
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, table);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{
@ -119,11 +102,6 @@ public class MetaDataAction
String processName = entry.getKey();
QProcessMetaData process = entry.getValue();
if(!filter.allowProcess(metaDataInput, process))
{
continue;
}
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, process);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{
@ -144,11 +122,6 @@ public class MetaDataAction
String reportName = entry.getKey();
QReportMetaData report = entry.getValue();
if(!filter.allowReport(metaDataInput, report))
{
continue;
}
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, report);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{
@ -169,11 +142,6 @@ public class MetaDataAction
String widgetName = entry.getKey();
QWidgetMetaDataInterface widget = entry.getValue();
if(!filter.allowWidget(metaDataInput, widget))
{
continue;
}
PermissionCheckResult permissionResult = PermissionsHelper.getPermissionCheckResult(metaDataInput, widget);
if(permissionResult.equals(PermissionCheckResult.DENY_HIDE))
{
@ -206,19 +174,9 @@ public class MetaDataAction
continue;
}
if(!filter.allowApp(metaDataInput, app))
{
continue;
}
apps.put(appName, new QFrontendAppMetaData(app, metaDataOutput));
treeNodes.put(appName, new AppTreeNode(app));
//////////////////////////////////////
// build the frontend-app meta-data //
//////////////////////////////////////
QFrontendAppMetaData frontendAppMetaData = new QFrontendAppMetaData(app, metaDataOutput);
/////////////////////////////////////////
// add children (if they're permitted) //
/////////////////////////////////////////
if(CollectionUtils.nullSafeHasContents(app.getChildren()))
{
for(QAppChildMetaData child : app.getChildren())
@ -232,42 +190,9 @@ public class MetaDataAction
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// if the child was filtered away, so it isn't in its corresponding map, then don't include it here //
//////////////////////////////////////////////////////////////////////////////////////////////////////
if(child instanceof QTableMetaData table && !tables.containsKey(table.getName()))
{
continue;
}
if(child instanceof QProcessMetaData process && !processes.containsKey(process.getName()))
{
continue;
}
if(child instanceof QReportMetaData report && !reports.containsKey(report.getName()))
{
continue;
}
if(child instanceof QAppMetaData childApp && !apps.containsKey(childApp.getName()))
{
// continue;
}
frontendAppMetaData.addChild(new AppTreeNode(child));
apps.get(appName).addChild(new AppTreeNode(child));
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////
// if the app ended up having no children, then discard it //
// todo - i think this was wrong, because it didn't take into account ... something nested maybe... //
//////////////////////////////////////////////////////////////////////////////////////////////////////
if(CollectionUtils.nullSafeIsEmpty(frontendAppMetaData.getChildren()) && CollectionUtils.nullSafeIsEmpty(frontendAppMetaData.getWidgets()))
{
// LOG.debug("Discarding empty app", logPair("name", frontendAppMetaData.getName()));
// continue;
}
apps.put(appName, frontendAppMetaData);
treeNodes.put(appName, new AppTreeNode(app));
}
metaDataOutput.setApps(apps);
@ -303,33 +228,6 @@ public class MetaDataAction
/***************************************************************************
**
***************************************************************************/
private MetaDataFilterInterface getMetaDataFilter()
{
return metaDataFilterMemoization.getResult(QContext.getQInstance(), i ->
{
MetaDataFilterInterface filter = null;
QCodeReference metaDataFilterReference = QContext.getQInstance().getMetaDataFilter();
if(metaDataFilterReference != null)
{
filter = QCodeLoader.getAdHoc(MetaDataFilterInterface.class, metaDataFilterReference);
LOG.debug("Using new meta-data filter of type: " + filter.getClass().getSimpleName());
}
if(filter == null)
{
filter = new AllowAllMetaDataFilter();
LOG.debug("Using new default (allow-all) meta-data filter");
}
return (filter);
}).orElseThrow(() -> new QRuntimeException("Error getting metaDataFilter"));
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -1,64 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.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
{
/***************************************************************************
**
***************************************************************************/
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);
}

View File

@ -40,7 +40,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReferenceLambda;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
@ -258,20 +257,11 @@ public class RunBackendStepAction
{
runBackendStepOutput.seedFromRequest(runBackendStepInput);
Object codeObject;
if(code instanceof QCodeReferenceLambda<?> qCodeReferenceLambda)
{
codeObject = qCodeReferenceLambda.getLambda();
}
else
{
Class<?> codeClass = Class.forName(code.getName());
codeObject = codeClass.getConstructor().newInstance();
}
Class<?> codeClass = Class.forName(code.getName());
Object codeObject = codeClass.getConstructor().newInstance();
if(!(codeObject instanceof BackendStep backendStepCodeObject))
{
throw (new QException("The supplied codeReference [" + code + "] is not a reference to a BackendStep"));
throw (new QException("The supplied code [" + codeClass.getName() + "] is not an instance of BackendStep"));
}
backendStepCodeObject.run(runBackendStepInput, runBackendStepOutput);

View File

@ -28,11 +28,9 @@ import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.ActionHelper;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.NoCodeWidgetRenderer;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
@ -54,18 +52,15 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.update.UpdateInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.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.processes.NoCodeWidgetFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStateMachineStep;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.processes.implementations.basepull.BasepullConfiguration;
import com.kingsrook.qqq.backend.core.processes.tracing.ProcessTracerInterface;
import com.kingsrook.qqq.backend.core.state.InMemoryStateProvider;
import com.kingsrook.qqq.backend.core.state.StateProviderInterface;
import com.kingsrook.qqq.backend.core.state.StateType;
@ -74,7 +69,6 @@ import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.commons.lang.BooleanUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
@ -91,16 +85,12 @@ public class RunProcessAction
public static final String BASEPULL_TIMESTAMP_FIELD = "basepullTimestampField";
public static final String BASEPULL_CONFIGURATION = "basepullConfiguration";
public static final String PROCESS_TRACER_CODE_REFERENCE_FIELD = "processTracerCodeReference";
////////////////////////////////////////////////////////////////////////////////////////////////
// indicator that the timestamp field should be updated - e.g., the execute step is finished. //
////////////////////////////////////////////////////////////////////////////////////////////////
public static final String BASEPULL_READY_TO_UPDATE_TIMESTAMP_FIELD = "basepullReadyToUpdateTimestamp";
public static final String BASEPULL_DID_QUERY_USING_TIMESTAMP_FIELD = "basepullDidQueryUsingTimestamp";
private ProcessTracerInterface processTracer;
/*******************************************************************************
@ -127,17 +117,9 @@ public class RunProcessAction
}
runProcessOutput.setProcessUUID(runProcessInput.getProcessUUID());
traceStartOrResume(runProcessInput, process);
UUIDAndTypeStateKey stateKey = new UUIDAndTypeStateKey(UUID.fromString(runProcessInput.getProcessUUID()), StateType.PROCESS_STATUS);
ProcessState processState = primeProcessState(runProcessInput, stateKey, process);
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// these should always be clear when we're starting a run - so make sure they haven't leaked from previous //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
processState.clearNextStepName();
processState.clearBackStepName();
/////////////////////////////////////////////////////////
// if process is 'basepull' style, keep track of 'now' //
/////////////////////////////////////////////////////////
@ -152,11 +134,90 @@ public class RunProcessAction
try
{
switch(Objects.requireNonNull(process.getStepFlow(), "Process [" + process.getName() + "] has a null stepFlow."))
String lastStepName = runProcessInput.getStartAfterStep();
STEP_LOOP:
while(true)
{
case LINEAR -> runLinearStepLoop(process, processState, stateKey, runProcessInput, runProcessOutput);
case STATE_MACHINE -> runStateMachineStep(runProcessInput.getStartAfterStep(), process, processState, stateKey, runProcessInput, runProcessOutput, 0);
default -> throw (new QException("Unhandled process step flow: " + process.getStepFlow()));
///////////////////////////////////////////////////////////////////////////////////////////////////////
// always refresh the step list - as any step that runs can modify it (in the process state). //
// this is why we don't do a loop over the step list - as we'd get ConcurrentModificationExceptions. //
///////////////////////////////////////////////////////////////////////////////////////////////////////
List<QStepMetaData> stepList = getAvailableStepList(processState, process, lastStepName);
if(stepList.isEmpty())
{
break;
}
QStepMetaData step = stepList.get(0);
lastStepName = step.getName();
if(step instanceof QFrontendStepMetaData frontendStep)
{
////////////////////////////////////////////////////////////////
// Handle what to do with frontend steps, per request setting //
////////////////////////////////////////////////////////////////
switch(runProcessInput.getFrontendStepBehavior())
{
case BREAK ->
{
LOG.trace("Breaking process [" + process.getName() + "] at frontend step (as requested by caller): " + step.getName());
processFrontendStepFieldDefaultValues(processState, frontendStep);
processFrontendComponents(processState, frontendStep);
processState.setNextStepName(step.getName());
break STEP_LOOP;
}
case SKIP ->
{
LOG.trace("Skipping frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
//////////////////////////////////////////////////////////////////////
// much less error prone in case this code changes in the future... //
//////////////////////////////////////////////////////////////////////
// noinspection UnnecessaryContinue
continue;
}
case FAIL ->
{
LOG.trace("Throwing error for frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
throw (new QException("Failing process at step " + step.getName() + " (as requested, to fail on frontend steps)"));
}
default -> throw new IllegalStateException("Unexpected value: " + runProcessInput.getFrontendStepBehavior());
}
}
else if(step instanceof QBackendStepMetaData backendStepMetaData)
{
///////////////////////
// Run backend steps //
///////////////////////
LOG.debug("Running backend step [" + step.getName() + "] in process [" + process.getName() + "]");
RunBackendStepOutput runBackendStepOutput = runBackendStep(runProcessInput, process, runProcessOutput, stateKey, backendStepMetaData, process, processState);
/////////////////////////////////////////////////////////////////////////////////////////
// if the step returned an override lastStepName, use that to determine how we proceed //
/////////////////////////////////////////////////////////////////////////////////////////
if(runBackendStepOutput.getOverrideLastStepName() != null)
{
LOG.debug("Process step [" + lastStepName + "] returned an overrideLastStepName [" + runBackendStepOutput.getOverrideLastStepName() + "]!");
lastStepName = runBackendStepOutput.getOverrideLastStepName();
}
/////////////////////////////////////////////////////////////////////////////////////////////
// similarly, if the step produced an updatedFrontendStepList, propagate that data outward //
/////////////////////////////////////////////////////////////////////////////////////////////
if(runBackendStepOutput.getUpdatedFrontendStepList() != null)
{
LOG.debug("Process step [" + lastStepName + "] generated an updatedFrontendStepList [" + runBackendStepOutput.getUpdatedFrontendStepList().stream().map(s -> s.getName()).toList() + "]!");
runProcessOutput.setUpdatedFrontendStepList(runBackendStepOutput.getUpdatedFrontendStepList());
}
}
else
{
//////////////////////////////////////////////////
// in case we have a different step type, throw //
//////////////////////////////////////////////////
throw (new QException("Unsure how to run a step of type: " + step.getClass().getName()));
}
}
///////////////////////////////////////////////////////////////////////////
@ -176,7 +237,6 @@ public class RunProcessAction
////////////////////////////////////////////////////////////
// upon exception (e.g., one thrown by a step), throw it. //
////////////////////////////////////////////////////////////
traceBreakOrFinish(runProcessInput, runProcessOutput, qe);
throw (qe);
}
catch(Exception e)
@ -184,7 +244,6 @@ public class RunProcessAction
////////////////////////////////////////////////////////////
// upon exception (e.g., one thrown by a step), throw it. //
////////////////////////////////////////////////////////////
traceBreakOrFinish(runProcessInput, runProcessOutput, e);
throw (new QException("Error running process", e));
}
finally
@ -195,317 +254,11 @@ public class RunProcessAction
runProcessOutput.setProcessState(processState);
}
traceBreakOrFinish(runProcessInput, runProcessOutput, null);
return (runProcessOutput);
}
/***************************************************************************
**
***************************************************************************/
private void runLinearStepLoop(QProcessMetaData process, ProcessState processState, UUIDAndTypeStateKey stateKey, RunProcessInput runProcessInput, RunProcessOutput runProcessOutput) throws Exception
{
String lastStepName = runProcessInput.getStartAfterStep();
String startAtStep = runProcessInput.getStartAtStep();
while(true)
{
///////////////////////////////////////////////////////////////////////////////////////////////////////
// always refresh the step list - as any step that runs can modify it (in the process state). //
// this is why we don't do a loop over the step list - as we'd get ConcurrentModificationExceptions. //
// deal with if we were told, from the input, to start After a step, or start At a step. //
///////////////////////////////////////////////////////////////////////////////////////////////////////
List<QStepMetaData> stepList;
if(startAtStep == null)
{
stepList = getAvailableStepList(processState, process, lastStepName, false);
}
else
{
stepList = getAvailableStepList(processState, process, startAtStep, true);
///////////////////////////////////////////////////////////////////////////////////
// clear this field - so after we run a step, we'll then loop in last-step mode. //
///////////////////////////////////////////////////////////////////////////////////
startAtStep = null;
///////////////////////////////////////////////////////////////////////////////////
// if we're going to run a backend step now, let it see that this is a step-back //
///////////////////////////////////////////////////////////////////////////////////
processState.setIsStepBack(true);
}
if(stepList.isEmpty())
{
break;
}
QStepMetaData step = stepList.get(0);
lastStepName = step.getName();
if(step instanceof QFrontendStepMetaData frontendStep)
{
LoopTodo loopTodo = prepareForFrontendStep(runProcessInput, process, frontendStep, processState);
if(loopTodo == LoopTodo.BREAK)
{
break;
}
}
else if(step instanceof QBackendStepMetaData backendStepMetaData)
{
RunBackendStepOutput runBackendStepOutput = runBackendStep(process, processState, stateKey, runProcessInput, runProcessOutput, backendStepMetaData, step);
/////////////////////////////////////////////////////////////////////////////////////////
// if the step returned an override lastStepName, use that to determine how we proceed //
/////////////////////////////////////////////////////////////////////////////////////////
if(runBackendStepOutput.getOverrideLastStepName() != null)
{
LOG.debug("Process step [" + lastStepName + "] returned an overrideLastStepName [" + runBackendStepOutput.getOverrideLastStepName() + "]!");
lastStepName = runBackendStepOutput.getOverrideLastStepName();
}
}
else
{
//////////////////////////////////////////////////
// in case we have a different step type, throw //
//////////////////////////////////////////////////
throw (new QException("Unsure how to run a step of type: " + step.getClass().getName()));
}
////////////////////////////////////////////////////////////////////////////////////////
// only let this value be set for the original back step - don't let it stick around. //
// if a process wants to keep track of this itself, it can, but in a different slot. //
////////////////////////////////////////////////////////////////////////////////////////
processState.setIsStepBack(false);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// in case we broke from the loop above (e.g., by going directly into a frontend step), once again make sure to lower this flag. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
processState.setIsStepBack(false);
}
/***************************************************************************
**
***************************************************************************/
private enum LoopTodo
{
BREAK,
CONTINUE
}
/***************************************************************************
**
***************************************************************************/
private LoopTodo prepareForFrontendStep(RunProcessInput runProcessInput, QProcessMetaData process, QFrontendStepMetaData step, ProcessState processState) throws QException
{
////////////////////////////////////////////////////////////////
// Handle what to do with frontend steps, per request setting //
////////////////////////////////////////////////////////////////
switch(runProcessInput.getFrontendStepBehavior())
{
case BREAK ->
{
LOG.trace("Breaking process [" + process.getName() + "] at frontend step (as requested by caller): " + step.getName());
processFrontendStepFieldDefaultValues(processState, step);
processFrontendComponents(processState, step);
processState.setNextStepName(step.getName());
if(StringUtils.hasContent(step.getBackStepName()) && processState.getBackStepName().isEmpty())
{
processState.setBackStepName(step.getBackStepName());
}
return LoopTodo.BREAK;
}
case SKIP ->
{
LOG.trace("Skipping frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
return LoopTodo.CONTINUE;
}
case FAIL ->
{
LOG.trace("Throwing error for frontend step [" + step.getName() + "] in process [" + process.getName() + "] (as requested by caller)");
throw (new QException("Failing process at step " + step.getName() + " (as requested, to fail on frontend steps)"));
}
default -> throw new IllegalStateException("Unexpected value: " + runProcessInput.getFrontendStepBehavior());
}
}
/***************************************************************************
**
***************************************************************************/
private void runStateMachineStep(String lastStepName, QProcessMetaData process, ProcessState processState, UUIDAndTypeStateKey stateKey, RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, int stackDepth) throws Exception
{
//////////////////////////////
// check for stack-overflow //
//////////////////////////////
Integer maxStateMachineProcessStepFlowStackDepth = Objects.requireNonNullElse(runProcessInput.getValueInteger("maxStateMachineProcessStepFlowStackDepth"), 20);
if(stackDepth > maxStateMachineProcessStepFlowStackDepth)
{
throw (new QException("StateMachine process recurred too many times (exceeded maxStateMachineProcessStepFlowStackDepth of " + maxStateMachineProcessStepFlowStackDepth + ")"));
}
//////////////////////////////////
// figure out what step to run: //
//////////////////////////////////
QStepMetaData step = null;
if(!StringUtils.hasContent(lastStepName))
{
////////////////////////////////////////////////////////////////////
// if no lastStepName is given, start at the process's first step //
////////////////////////////////////////////////////////////////////
if(CollectionUtils.nullSafeIsEmpty(process.getStepList()))
{
throw (new QException("Process [" + process.getName() + "] does not have a step list defined."));
}
step = process.getStepList().get(0);
}
else
{
/////////////////////////////////////
// else run the given lastStepName //
/////////////////////////////////////
processState.clearNextStepName();
processState.clearBackStepName();
step = process.getStep(lastStepName);
if(step == null)
{
throw (new QException("Could not find step by name [" + lastStepName + "]"));
}
}
/////////////////////////////////////////////////////////////////////////
// for the flow of: //
// we were on a frontend step (as a sub-step of a state machine step), //
// and now we're here to run that state-step's backend step - //
// find the state-machine step containing this frontend step. //
/////////////////////////////////////////////////////////////////////////
String skipSubStepsUntil = null;
if(step instanceof QFrontendStepMetaData frontendStepMetaData)
{
QStateMachineStep stateMachineStep = getStateMachineStepContainingSubStep(process, frontendStepMetaData.getName());
if(stateMachineStep == null)
{
throw (new QException("Could not find stateMachineStep that contains last-frontend step: " + frontendStepMetaData.getName()));
}
step = stateMachineStep;
//////////////////////////////////////////////////////////////////////////////////
// set this flag, to know to skip this frontend step in the sub-step loop below //
//////////////////////////////////////////////////////////////////////////////////
skipSubStepsUntil = frontendStepMetaData.getName();
}
if(!(step instanceof QStateMachineStep stateMachineStep))
{
throw (new QException("Have a non-stateMachineStep in a process using stateMachine flow... " + step.getClass().getName()));
}
///////////////////////
// run the sub-steps //
///////////////////////
boolean ranAnySubSteps = false;
for(QStepMetaData subStep : stateMachineStep.getSubSteps())
{
///////////////////////////////////////////////////////////////////////////////////////////////
// ok, well, skip them if this flag is set (and clear the flag once we've hit this sub-step) //
///////////////////////////////////////////////////////////////////////////////////////////////
if(skipSubStepsUntil != null)
{
if(skipSubStepsUntil.equals(subStep.getName()))
{
skipSubStepsUntil = null;
}
continue;
}
ranAnySubSteps = true;
if(subStep instanceof QFrontendStepMetaData frontendStep)
{
LoopTodo loopTodo = prepareForFrontendStep(runProcessInput, process, frontendStep, processState);
if(loopTodo == LoopTodo.BREAK)
{
return;
}
}
else if(subStep instanceof QBackendStepMetaData backendStepMetaData)
{
RunBackendStepOutput runBackendStepOutput = runBackendStep(process, processState, stateKey, runProcessInput, runProcessOutput, backendStepMetaData, step);
Optional<String> nextStepName = runBackendStepOutput.getProcessState().getNextStepName();
if(nextStepName.isEmpty() && StringUtils.hasContent(stateMachineStep.getDefaultNextStepName()))
{
nextStepName = Optional.of(stateMachineStep.getDefaultNextStepName());
}
if(nextStepName.isPresent())
{
//////////////////////////////////////////////////////////////////////////////////////////////////////
// if we've been given a next-step-name, go to that step now. //
// it might be a backend-only stateMachineStep, in which case, we should run that backend step now. //
// or it might be a frontend-then-backend step, in which case, we want to go to that frontend step. //
// if we weren't given a next-step-name, then we should stay in the same state - either to finish //
// its sub-steps, or, to fall out of the loop and end the process. //
//////////////////////////////////////////////////////////////////////////////////////////////////////
processState.clearNextStepName();
processState.clearBackStepName();
runStateMachineStep(nextStepName.get(), process, processState, stateKey, runProcessInput, runProcessOutput, stackDepth + 1);
return;
}
}
else
{
//////////////////////////////////////////////////
// in case we have a different step type, throw //
//////////////////////////////////////////////////
throw (new QException("Unsure how to run a step of type: " + step.getClass().getName()));
}
}
if(!ranAnySubSteps)
{
if(StringUtils.hasContent(stateMachineStep.getDefaultNextStepName()))
{
runStateMachineStep(stateMachineStep.getDefaultNextStepName(), process, processState, stateKey, runProcessInput, runProcessOutput, stackDepth + 1);
}
}
}
/*******************************************************************************
**
*******************************************************************************/
public QStateMachineStep getStateMachineStepContainingSubStep(QProcessMetaData process, String stepName)
{
for(QStepMetaData step : process.getAllSteps().values())
{
if(step instanceof QStateMachineStep stateMachineStep)
{
for(QStepMetaData subStep : stateMachineStep.getSubSteps())
{
if(subStep.getName().equals(stepName))
{
return (stateMachineStep);
}
}
}
}
return (null);
}
/*******************************************************************************
**
*******************************************************************************/
@ -583,12 +336,12 @@ public class RunProcessAction
///////////////////////////////////////////////////
runProcessInput.seedFromProcessState(optionalProcessState.get());
/////////////////////////////////////////////////////////////////////////////////////////////////////
// if we're restoring an old state, we can discard a previously stored processMetaDataAdjustment - //
// it is only needed on the transitional edge from a backend-step to a frontend step, but not //
// in the other directly //
/////////////////////////////////////////////////////////////////////////////////////////////////////
optionalProcessState.get().setProcessMetaDataAdjustment(null);
///////////////////////////////////////////////////////////////////////////////////////////////////
// if we're restoring an old state, we can discard a previously stored updatedFrontendStepList - //
// it is only needed on the transitional edge from a backend-step to a frontend step, but not //
// in the other directly //
///////////////////////////////////////////////////////////////////////////////////////////////////
optionalProcessState.get().setUpdatedFrontendStepList(null);
///////////////////////////////////////////////////////////////////////////
// if there were values from the caller, put those (back) in the request //
@ -603,40 +356,16 @@ public class RunProcessAction
}
ProcessState processState = optionalProcessState.get();
processState.clearNextStepName();
return processState;
}
/***************************************************************************
**
***************************************************************************/
private RunBackendStepOutput runBackendStep(QProcessMetaData process, ProcessState processState, UUIDAndTypeStateKey stateKey, RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, QBackendStepMetaData backendStepMetaData, QStepMetaData step) throws Exception
{
///////////////////////
// Run backend steps //
///////////////////////
LOG.debug("Running backend step [" + step.getName() + "] in process [" + process.getName() + "]");
RunBackendStepOutput runBackendStepOutput = runBackendStep(runProcessInput, process, runProcessOutput, stateKey, backendStepMetaData, process, processState);
//////////////////////////////////////////////////////////////////////////////////////////////
// similarly, if the step produced a processMetaDataAdjustment, propagate that data outward //
//////////////////////////////////////////////////////////////////////////////////////////////
if(runBackendStepOutput.getProcessMetaDataAdjustment() != null)
{
LOG.debug("Process step [" + step.getName() + "] generated a ProcessMetaDataAdjustment [" + runBackendStepOutput.getProcessMetaDataAdjustment() + "]!");
runProcessOutput.setProcessMetaDataAdjustment(runBackendStepOutput.getProcessMetaDataAdjustment());
}
return runBackendStepOutput;
}
/*******************************************************************************
** Run a single backend step.
*******************************************************************************/
RunBackendStepOutput runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, QProcessMetaData qProcessMetaData, ProcessState processState) throws Exception
protected RunBackendStepOutput runBackendStep(RunProcessInput runProcessInput, QProcessMetaData process, RunProcessOutput runProcessOutput, UUIDAndTypeStateKey stateKey, QBackendStepMetaData backendStep, QProcessMetaData qProcessMetaData, ProcessState processState) throws Exception
{
RunBackendStepInput runBackendStepInput = new RunBackendStepInput(processState);
runBackendStepInput.setProcessName(process.getName());
@ -644,7 +373,6 @@ public class RunProcessAction
runBackendStepInput.setCallback(runProcessInput.getCallback());
runBackendStepInput.setFrontendStepBehavior(runProcessInput.getFrontendStepBehavior());
runBackendStepInput.setAsyncJobCallback(runProcessInput.getAsyncJobCallback());
runBackendStepInput.setProcessTracer(processTracer);
runBackendStepInput.setTableName(process.getTableName());
if(!StringUtils.hasContent(runBackendStepInput.getTableName()))
@ -666,13 +394,9 @@ public class RunProcessAction
runBackendStepInput.setBasepullLastRunTime((Instant) runProcessInput.getValues().get(BASEPULL_LAST_RUNTIME_KEY));
}
traceStepStart(runBackendStepInput);
RunBackendStepOutput runBackendStepOutput = new RunBackendStepAction().execute(runBackendStepInput);
storeState(stateKey, runBackendStepOutput.getProcessState());
traceStepFinish(runBackendStepInput, runBackendStepOutput);
if(runBackendStepOutput.getException() != null)
{
runProcessOutput.setException(runBackendStepOutput.getException());
@ -686,10 +410,8 @@ public class RunProcessAction
/*******************************************************************************
** Get the list of steps which are eligible to run.
**
** lastStep will be included in the list, or not, based on includeLastStep.
*******************************************************************************/
static List<QStepMetaData> getAvailableStepList(ProcessState processState, QProcessMetaData process, String lastStep, boolean includeLastStep) throws QException
private List<QStepMetaData> getAvailableStepList(ProcessState processState, QProcessMetaData process, String lastStep) throws QException
{
if(lastStep == null)
{
@ -716,10 +438,6 @@ public class RunProcessAction
if(stepName.equals(lastStep))
{
foundLastStep = true;
if(includeLastStep)
{
validStepNames.add(stepName);
}
}
}
return (stepNamesToSteps(process, validStepNames));
@ -731,7 +449,7 @@ public class RunProcessAction
/*******************************************************************************
**
*******************************************************************************/
private static List<QStepMetaData> stepNamesToSteps(QProcessMetaData process, List<String> stepNames) throws QException
private List<QStepMetaData> stepNamesToSteps(QProcessMetaData process, List<String> stepNames) throws QException
{
List<QStepMetaData> result = new ArrayList<>();
@ -815,14 +533,13 @@ public class RunProcessAction
{
QSession session = QContext.getQSession();
QBackendMetaData backendMetaData = QContext.getQInstance().getBackend(process.getVariantBackend());
String variantTypeKey = backendMetaData.getBackendVariantsConfig().getVariantTypeKey();
if(session.getBackendVariants() == null || !session.getBackendVariants().containsKey(variantTypeKey))
if(session.getBackendVariants() == null || !session.getBackendVariants().containsKey(backendMetaData.getVariantOptionsTableTypeValue()))
{
LOG.warn("Could not find Backend Variant information for Backend '" + backendMetaData.getName() + "'");
}
else
{
basepullKeyValue += "-" + session.getBackendVariants().get(variantTypeKey);
basepullKeyValue += "-" + session.getBackendVariants().get(backendMetaData.getVariantOptionsTableTypeValue());
}
}
@ -951,153 +668,4 @@ public class RunProcessAction
runProcessInput.getValues().put(BASEPULL_TIMESTAMP_FIELD, basepullConfiguration.getTimestampField());
runProcessInput.getValues().put(BASEPULL_CONFIGURATION, basepullConfiguration);
}
/***************************************************************************
**
***************************************************************************/
private void setupProcessTracer(RunProcessInput runProcessInput, QProcessMetaData process)
{
try
{
if(process.getProcessTracerCodeReference() != null)
{
processTracer = QCodeLoader.getAdHoc(ProcessTracerInterface.class, process.getProcessTracerCodeReference());
}
Serializable processTracerCodeReference = runProcessInput.getValue(PROCESS_TRACER_CODE_REFERENCE_FIELD);
if(processTracerCodeReference != null)
{
if(processTracerCodeReference instanceof QCodeReference codeReference)
{
processTracer = QCodeLoader.getAdHoc(ProcessTracerInterface.class, codeReference);
}
}
}
catch(Exception e)
{
LOG.warn("Error setting up processTracer", e, logPair("processName", runProcessInput.getProcessName()));
}
}
/***************************************************************************
**
***************************************************************************/
private void traceStartOrResume(RunProcessInput runProcessInput, QProcessMetaData process)
{
setupProcessTracer(runProcessInput, process);
try
{
if(processTracer != null)
{
if(StringUtils.hasContent(runProcessInput.getStartAfterStep()) || StringUtils.hasContent(runProcessInput.getStartAtStep()))
{
processTracer.handleProcessResume(runProcessInput);
}
else
{
processTracer.handleProcessStart(runProcessInput);
}
}
}
catch(Exception e)
{
LOG.info("Error in traceStart", e, logPair("processName", runProcessInput.getProcessName()));
}
}
/***************************************************************************
**
***************************************************************************/
private void traceBreakOrFinish(RunProcessInput runProcessInput, RunProcessOutput runProcessOutput, Exception processException)
{
try
{
if(processTracer != null)
{
ProcessState processState = runProcessOutput.getProcessState();
boolean isBreak = true;
/////////////////////////////////////////////////////////////
// if there's no next step, that means the process is done //
/////////////////////////////////////////////////////////////
if(processState.getNextStepName().isEmpty())
{
isBreak = false;
}
else
{
/////////////////////////////////////////////////////////////////
// or if the next step is the last index, then we're also done //
/////////////////////////////////////////////////////////////////
String nextStepName = processState.getNextStepName().get();
int nextStepIndex = processState.getStepList().indexOf(nextStepName);
if(nextStepIndex == processState.getStepList().size() - 1)
{
isBreak = false;
}
}
if(isBreak)
{
processTracer.handleProcessBreak(runProcessInput, runProcessOutput, processException);
}
else
{
processTracer.handleProcessFinish(runProcessInput, runProcessOutput, processException);
}
}
}
catch(Exception e)
{
LOG.info("Error in traceProcessFinish", e, logPair("processName", runProcessInput.getProcessName()));
}
}
/***************************************************************************
**
***************************************************************************/
private void traceStepStart(RunBackendStepInput runBackendStepInput)
{
try
{
if(processTracer != null)
{
processTracer.handleStepStart(runBackendStepInput);
}
}
catch(Exception e)
{
LOG.info("Error in traceStepFinish", e, logPair("processName", runBackendStepInput.getProcessName()), logPair("stepName", runBackendStepInput.getStepName()));
}
}
/***************************************************************************
**
***************************************************************************/
private void traceStepFinish(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput)
{
try
{
if(processTracer != null)
{
processTracer.handleStepFinish(runBackendStepInput, runBackendStepOutput);
}
}
catch(Exception e)
{
LOG.info("Error in traceStepFinish", e, logPair("processName", runBackendStepInput.getProcessName()), logPair("stepName", runBackendStepInput.getStepName()));
}
}
}

View File

@ -71,7 +71,6 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAndJoinTable;
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.reporting.QReportDataSource;
@ -568,7 +567,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
// all pivotFields that are possible value sources are implicitly translated //
///////////////////////////////////////////////////////////////////////////////
QTableMetaData mainTable = QContext.getQInstance().getTable(dataSource.getSourceTable());
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(mainTable, summaryFieldName);
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(mainTable, summaryFieldName);
if(fieldAndJoinTable.field().getPossibleValueSourceName() != null)
{
fieldsToTranslatePossibleValues.add(summaryFieldName);
@ -581,6 +580,32 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
/*******************************************************************************
**
*******************************************************************************/
public static FieldAndJoinTable getFieldAndJoinTable(QTableMetaData mainTable, String fieldName) throws QException
{
if(fieldName.indexOf('.') > -1)
{
String joinTableName = fieldName.replaceAll("\\..*", "");
String joinFieldName = fieldName.replaceAll(".*\\.", "");
QTableMetaData joinTable = QContext.getQInstance().getTable(joinTableName);
if(joinTable == null)
{
throw (new QException("Unrecognized join table name: " + joinTableName));
}
return new FieldAndJoinTable(joinTable.getField(joinFieldName), joinTable);
}
else
{
return new FieldAndJoinTable(mainTable.getField(fieldName), mainTable);
}
}
/*******************************************************************************
**
*******************************************************************************/
@ -731,7 +756,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
SummaryKey key = new SummaryKey();
for(String summaryFieldName : view.getSummaryFields())
{
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, summaryFieldName);
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(table, summaryFieldName);
Serializable summaryValue = record.getValue(summaryFieldName);
if(fieldAndJoinTable.field().getPossibleValueSourceName() != null)
{
@ -786,7 +811,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
//////////////////////////////////////////////////////
// todo - memoize this, if we ever need to optimize //
//////////////////////////////////////////////////////
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, fieldName);
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(table, fieldName);
field = fieldAndJoinTable.field();
}
catch(Exception e)
@ -931,7 +956,7 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
List<QFieldMetaData> fields = new ArrayList<>();
for(String summaryFieldName : view.getSummaryFields())
{
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, summaryFieldName);
FieldAndJoinTable fieldAndJoinTable = getFieldAndJoinTable(table, summaryFieldName);
fields.add(new QFieldMetaData(summaryFieldName, fieldAndJoinTable.field().getType()).withLabel(fieldAndJoinTable.field().getLabel())); // todo do we need the type? if so need table as input here
}
for(QReportField column : view.getColumns())
@ -1183,4 +1208,27 @@ public class GenerateReportAction extends AbstractQActionFunction<ReportInput, R
{
}
/*******************************************************************************
**
*******************************************************************************/
public record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable)
{
/*******************************************************************************
**
*******************************************************************************/
public String getLabel(QTableMetaData mainTable)
{
if(mainTable.getName().equals(joinTable.getName()))
{
return (field.getLabel());
}
else
{
return (joinTable.getLabel() + ": " + field.getLabel());
}
}
}
}

View File

@ -82,11 +82,6 @@ public class DeleteAction
{
ActionHelper.validateSession(deleteInput);
if(deleteInput.getTableName() == null)
{
throw (new QException("Table name was not specified in delete input"));
}
QTableMetaData table = deleteInput.getTable();
String primaryKeyFieldName = table.getPrimaryKeyField();
QFieldMetaData primaryKeyField = table.getField(primaryKeyFieldName);
@ -325,7 +320,7 @@ public class DeleteAction
QTableMetaData table = deleteInput.getTable();
List<QRecord> primaryKeysNotFound = validateRecordsExistAndCanBeAccessed(deleteInput, oldRecordList.get());
ValidateRecordSecurityLockHelper.validateSecurityFields(table, oldRecordList.get(), ValidateRecordSecurityLockHelper.Action.DELETE, deleteInput.getTransaction());
ValidateRecordSecurityLockHelper.validateSecurityFields(table, oldRecordList.get(), ValidateRecordSecurityLockHelper.Action.DELETE);
///////////////////////////////////////////////////////////////////////////
// after all validations, run the pre-delete customizer, if there is one //

View File

@ -67,7 +67,6 @@ import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -111,12 +110,6 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
public InsertOutput execute(InsertInput insertInput) throws QException
{
ActionHelper.validateSession(insertInput);
if(!StringUtils.hasContent(insertInput.getTableName()))
{
throw (new QException("Table name was not specified in update input"));
}
QTableMetaData table = insertInput.getTable();
if(table == null)
@ -129,7 +122,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
/////////////////////////////
// run standard validators //
/////////////////////////////
performValidations(insertInput, false, false);
performValidations(insertInput, false);
//////////////////////////////////////////////////////
// use the backend module to actually do the insert //
@ -232,7 +225,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
/*******************************************************************************
**
*******************************************************************************/
public void performValidations(InsertInput insertInput, boolean isPreview, boolean didAlreadyRunCustomizer) throws QException
public void performValidations(InsertInput insertInput, boolean isPreview) throws QException
{
if(CollectionUtils.nullSafeIsEmpty(insertInput.getRecords()))
{
@ -244,10 +237,12 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
///////////////////////////////////////////////////////////////////
// load the pre-insert customizer and set it up, if there is one //
// then we'll run it based on its WhenToRun value //
// note - if we already ran it, then don't re-run it! //
///////////////////////////////////////////////////////////////////
Optional<TableCustomizerInterface> preInsertCustomizer = didAlreadyRunCustomizer ? Optional.empty() : QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_INSERT_RECORD.getRole());
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_ALL_VALIDATIONS);
Optional<TableCustomizerInterface> preInsertCustomizer = QCodeLoader.getTableCustomizer(table, TableCustomizers.PRE_INSERT_RECORD.getRole());
if(preInsertCustomizer.isPresent())
{
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_ALL_VALIDATIONS);
}
setDefaultValuesInRecords(table, insertInput.getRecords());
@ -263,7 +258,7 @@ public class InsertAction extends AbstractQActionFunction<InsertInput, InsertOut
}
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.BEFORE_SECURITY_CHECKS);
ValidateRecordSecurityLockHelper.validateSecurityFields(insertInput.getTable(), insertInput.getRecords(), ValidateRecordSecurityLockHelper.Action.INSERT, insertInput.getTransaction());
ValidateRecordSecurityLockHelper.validateSecurityFields(insertInput.getTable(), insertInput.getRecords(), ValidateRecordSecurityLockHelper.Action.INSERT);
runPreInsertCustomizerIfItIsTime(insertInput, isPreview, preInsertCustomizer, AbstractPreInsertCustomizer.WhenToRun.AFTER_ALL_VALIDATIONS);
}

View File

@ -47,8 +47,7 @@ public class StorageAction
{
/*******************************************************************************
** create an output stream in the storage backend - that can be written to,
** for the purpose of inserting or writing a file into storage.
**
*******************************************************************************/
public OutputStream createOutputStream(StorageInput storageInput) throws QException
{
@ -60,8 +59,7 @@ public class StorageAction
/*******************************************************************************
** create an input stream in the storage backend - that can be read from,
** for the purpose of getting or reading a file from storage.
**
*******************************************************************************/
public InputStream getInputStream(StorageInput storageInput) throws QException
{

View File

@ -74,7 +74,6 @@ import com.kingsrook.qqq.backend.core.model.statusmessages.QWarningMessage;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import org.apache.commons.lang.BooleanUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -119,11 +118,6 @@ public class UpdateAction
{
ActionHelper.validateSession(updateInput);
if(!StringUtils.hasContent(updateInput.getTableName()))
{
throw (new QException("Table name was not specified in update input"));
}
QTableMetaData table = updateInput.getTable();
//////////////////////////////////////////////////////
@ -267,7 +261,7 @@ public class UpdateAction
}
else
{
ValidateRecordSecurityLockHelper.validateSecurityFields(table, updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE, updateInput.getTransaction());
ValidateRecordSecurityLockHelper.validateSecurityFields(table, updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
}
if(updateInput.getInputSource().shouldValidateRequiredFields())
@ -380,7 +374,7 @@ public class UpdateAction
}
}
ValidateRecordSecurityLockHelper.validateSecurityFields(table, updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE, updateInput.getTransaction());
ValidateRecordSecurityLockHelper.validateSecurityFields(table, updateInput.getRecords(), ValidateRecordSecurityLockHelper.Action.UPDATE);
for(QRecord record : page)
{

View File

@ -31,12 +31,16 @@ import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QMetaDataVariableInterpreter;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.get.GetOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterOrderBy;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
@ -50,10 +54,12 @@ import com.kingsrook.qqq.backend.core.model.querystats.QueryStatCriteriaField;
import com.kingsrook.qqq.backend.core.model.querystats.QueryStatJoinTable;
import com.kingsrook.qqq.backend.core.model.querystats.QueryStatOrderByField;
import com.kingsrook.qqq.backend.core.model.session.QSession;
import com.kingsrook.qqq.backend.core.model.tables.QQQTableTableManager;
import com.kingsrook.qqq.backend.core.model.tables.QQQTable;
import com.kingsrook.qqq.backend.core.model.tables.QQQTablesMetaDataProvider;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.PrefixedDefaultThreadFactory;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qqq.backend.core.utils.collections.MapBuilder;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -365,7 +371,7 @@ public class QueryStatManager
//////////////////////
// set the table id //
//////////////////////
Integer qqqTableId = QQQTableTableManager.getQQQTableId(getInstance().qInstance, queryStat.getTableName());
Integer qqqTableId = getQQQTableId(queryStat.getTableName());
queryStat.setQqqTableId(qqqTableId);
//////////////////////////////
@ -376,7 +382,7 @@ public class QueryStatManager
List<QueryStatJoinTable> queryStatJoinTableList = new ArrayList<>();
for(String joinTableName : queryStat.getJoinTableNames())
{
queryStatJoinTableList.add(new QueryStatJoinTable().withQqqTableId(QQQTableTableManager.getQQQTableId(getInstance().qInstance, joinTableName)));
queryStatJoinTableList.add(new QueryStatJoinTable().withQqqTableId(getQQQTableId(joinTableName)));
}
queryStat.setQueryStatJoinTableList(queryStatJoinTableList);
}
@ -454,7 +460,7 @@ public class QueryStatManager
String[] parts = fieldName.split("\\.");
if(parts.length > 1)
{
queryStatCriteriaField.setQqqTableId(QQQTableTableManager.getQQQTableId(getInstance().qInstance, parts[0]));
queryStatCriteriaField.setQqqTableId(getQQQTableId(parts[0]));
queryStatCriteriaField.setName(parts[1]);
}
}
@ -492,7 +498,7 @@ public class QueryStatManager
String[] parts = fieldName.split("\\.");
if(parts.length > 1)
{
queryStatOrderByField.setQqqTableId(QQQTableTableManager.getQQQTableId(getInstance().qInstance, parts[0]));
queryStatOrderByField.setQqqTableId(getQQQTableId(parts[0]));
queryStatOrderByField.setName(parts[1]);
}
}
@ -506,6 +512,44 @@ public class QueryStatManager
}
}
}
/*******************************************************************************
**
*******************************************************************************/
private static Integer getQQQTableId(String tableName) throws QException
{
/////////////////////////////
// look in the cache table //
/////////////////////////////
GetInput getInput = new GetInput();
getInput.setTableName(QQQTablesMetaDataProvider.QQQ_TABLE_CACHE_TABLE_NAME);
getInput.setUniqueKey(MapBuilder.of("name", tableName));
GetOutput getOutput = new GetAction().execute(getInput);
////////////////////////
// upon cache miss... //
////////////////////////
if(getOutput.getRecord() == null)
{
///////////////////////////////////////////////////////
// insert the record (into the table, not the cache) //
///////////////////////////////////////////////////////
QTableMetaData tableMetaData = getInstance().qInstance.getTable(tableName);
InsertInput insertInput = new InsertInput();
insertInput.setTableName(QQQTable.TABLE_NAME);
insertInput.setRecords(List.of(new QRecord().withValue("name", tableName).withValue("label", tableMetaData.getLabel())));
InsertOutput insertOutput = new InsertAction().execute(insertInput);
///////////////////////////////////
// repeat the get from the cache //
///////////////////////////////////
getOutput = new GetAction().execute(getInput);
}
return getOutput.getRecord().getValueInteger("id");
}
}

View File

@ -50,7 +50,6 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
*******************************************************************************/
public class UniqueKeyHelper
{
private static Integer pageSize = 1000;
/*******************************************************************************
**
@ -61,71 +60,62 @@ public class UniqueKeyHelper
Map<List<Serializable>, Serializable> existingRecords = new HashMap<>();
if(ukFieldNames != null)
{
for(List<QRecord> page : CollectionUtils.getPages(recordList, pageSize))
QueryInput queryInput = new QueryInput();
queryInput.setTableName(table.getName());
queryInput.setTransaction(transaction);
QQueryFilter filter = new QQueryFilter();
if(ukFieldNames.size() == 1)
{
QueryInput queryInput = new QueryInput();
queryInput.setTableName(table.getName());
queryInput.setTransaction(transaction);
QQueryFilter filter = new QQueryFilter();
if(ukFieldNames.size() == 1)
List<Serializable> values = recordList.stream()
.filter(r -> CollectionUtils.nullSafeIsEmpty(r.getErrors()))
.map(r -> r.getValue(ukFieldNames.get(0)))
.collect(Collectors.toList());
filter.addCriteria(new QFilterCriteria(ukFieldNames.get(0), QCriteriaOperator.IN, values));
}
else
{
filter.setBooleanOperator(QQueryFilter.BooleanOperator.OR);
for(QRecord record : recordList)
{
List<Serializable> values = page.stream()
.filter(r -> CollectionUtils.nullSafeIsEmpty(r.getErrors()))
.map(r -> r.getValue(ukFieldNames.get(0)))
.collect(Collectors.toList());
if(values.isEmpty())
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
{
continue;
}
filter.addCriteria(new QFilterCriteria(ukFieldNames.get(0), QCriteriaOperator.IN, values));
}
else
{
filter.setBooleanOperator(QQueryFilter.BooleanOperator.OR);
for(QRecord record : page)
QQueryFilter subFilter = new QQueryFilter();
filter.addSubFilter(subFilter);
for(String fieldName : ukFieldNames)
{
if(CollectionUtils.nullSafeHasContents(record.getErrors()))
Serializable value = record.getValue(fieldName);
if(value == null)
{
continue;
subFilter.addCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.IS_BLANK));
}
QQueryFilter subFilter = new QQueryFilter();
filter.addSubFilter(subFilter);
for(String fieldName : ukFieldNames)
else
{
Serializable value = record.getValue(fieldName);
if(value == null)
{
subFilter.addCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.IS_BLANK));
}
else
{
subFilter.addCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, value));
}
subFilter.addCriteria(new QFilterCriteria(fieldName, QCriteriaOperator.EQUALS, value));
}
}
if(CollectionUtils.nullSafeIsEmpty(filter.getSubFilters()))
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if we didn't build any sub-filters (because all records have errors in them), don't run a query w/ no clauses - continue to next page //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
continue;
}
}
queryInput.setFilter(filter);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
for(QRecord record : queryOutput.getRecords())
if(CollectionUtils.nullSafeIsEmpty(filter.getSubFilters()))
{
Optional<List<Serializable>> keyValues = getKeyValues(table, uniqueKey, record, allowNullKeyValuesToEqual);
if(keyValues.isPresent())
{
existingRecords.put(keyValues.get(), record.getValue(table.getPrimaryKeyField()));
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if we didn't build any sub-filters (because all records have errors in them), don't run a query w/ no clauses - rather - return early. //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
return (existingRecords);
}
}
queryInput.setFilter(filter);
QueryOutput queryOutput = new QueryAction().execute(queryInput);
for(QRecord record : queryOutput.getRecords())
{
Optional<List<Serializable>> keyValues = getKeyValues(table, uniqueKey, record, allowNullKeyValuesToEqual);
if(keyValues.isPresent())
{
existingRecords.put(keyValues.get(), record.getValue(table.getPrimaryKeyField()));
}
}
}
@ -210,26 +200,4 @@ public class UniqueKeyHelper
}
}
/*******************************************************************************
** Getter for pageSize
**
*******************************************************************************/
public static Integer getPageSize()
{
return pageSize;
}
/*******************************************************************************
** Setter for pageSize
**
*******************************************************************************/
public static void setPageSize(Integer pageSize)
{
UniqueKeyHelper.pageSize = pageSize;
}
}

View File

@ -28,7 +28,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.actions.QBackendTransaction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
@ -84,7 +83,7 @@ public class ValidateRecordSecurityLockHelper
/*******************************************************************************
**
*******************************************************************************/
public static void validateSecurityFields(QTableMetaData table, List<QRecord> records, Action action, QBackendTransaction transaction) throws QException
public static void validateSecurityFields(QTableMetaData table, List<QRecord> records, Action action) throws QException
{
MultiRecordSecurityLock locksToCheck = getRecordSecurityLocks(table, action);
if(locksToCheck == null || CollectionUtils.nullSafeIsEmpty(locksToCheck.getLocks()))
@ -102,7 +101,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);
/////////////////////////////////
// propagate errors to records //
@ -142,7 +141,7 @@ public class ValidateRecordSecurityLockHelper
** BUT - WRITE locks - in their case, we read the record no matter what, and in
** here we need to verify we have a key that allows us to WRITE the record.
*******************************************************************************/
private static void evaluateRecordLocks(QTableMetaData table, List<QRecord> records, Action action, RecordSecurityLock recordSecurityLock, Map<Serializable, RecordWithErrors> errorRecords, List<Integer> treePosition, 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) throws QException
{
if(recordSecurityLock instanceof MultiRecordSecurityLock multiRecordSecurityLock)
{
@ -153,7 +152,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);
treePosition.remove(treePosition.size() - 1);
i++;
}
@ -226,7 +225,6 @@ public class ValidateRecordSecurityLockHelper
// query will be like (fkey1=? and fkey2=?) OR (fkey1=? and fkey2=?) OR (fkey1=? and fkey2=?) //
////////////////////////////////////////////////////////////////////////////////////////////////
QueryInput queryInput = new QueryInput();
queryInput.setTransaction(transaction);
queryInput.setTableName(leftMostJoin.getLeftTable());
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
queryInput.setFilter(filter);

View File

@ -104,21 +104,10 @@ public class RenderTemplateAction extends AbstractQActionFunction<RenderTemplate
/*******************************************************************************
** Static wrapper to render a Velocity template.
*******************************************************************************/
@Deprecated(since = "Call the version that doesn't take an ActionInput")
public static String renderVelocity(AbstractActionInput parentActionInput, Map<String, Object> context, String code) throws QException
{
return (renderVelocity(context, code));
}
/*******************************************************************************
** Most convenient static wrapper to render a Velocity template.
*******************************************************************************/
public static String renderVelocity(Map<String, Object> context, String code) throws QException
public static String renderVelocity(AbstractActionInput parentActionInput, Map<String, Object> context, String code) throws QException
{
return (render(TemplateType.VELOCITY, context, code));
}

View File

@ -23,8 +23,6 @@ package com.kingsrook.qqq.backend.core.actions.values;
import java.io.Serializable;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
@ -34,7 +32,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.logging.QLogger;
@ -471,8 +468,7 @@ public class QValueFormatter
{
for(QFieldMetaData field : table.getFields().values())
{
Optional<FieldAdornment> fileDownloadAdornment = field.getAdornment(AdornmentType.FILE_DOWNLOAD);
if(fileDownloadAdornment.isPresent())
if(field.getType().equals(QFieldType.BLOB))
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// file name comes from: //
@ -482,7 +478,20 @@ public class QValueFormatter
// - tableLabel primaryKey fieldLabel //
// - and - if the FILE_DOWNLOAD adornment had a DEFAULT_EXTENSION, then it gets added (preceded by a dot) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Map<String, Serializable> adornmentValues = fileDownloadAdornment.get().getValues();
Optional<FieldAdornment> fileDownloadAdornment = field.getAdornment(AdornmentType.FILE_DOWNLOAD);
Map<String, Serializable> adornmentValues = Collections.emptyMap();
if(fileDownloadAdornment.isPresent())
{
adornmentValues = fileDownloadAdornment.get().getValues();
}
else
{
///////////////////////////////////////////////////////
// don't change blobs unless they are file-downloads //
///////////////////////////////////////////////////////
continue;
}
String fileNameField = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FIELD));
String fileNameFormat = ValueUtils.getValueAsString(adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT));
@ -512,7 +521,7 @@ public class QValueFormatter
{
@SuppressWarnings("unchecked") // instance validation should make this safe!
List<String> fileNameFormatFields = (List<String>) adornmentValues.get(AdornmentType.FileDownloadValues.FILE_NAME_FORMAT_FIELDS);
List<String> values = CollectionUtils.nullSafeHasContents(fileNameFormatFields) ? fileNameFormatFields.stream().map(f -> ValueUtils.getValueAsString(record.getValue(f))).toList() : Collections.emptyList();
List<String> values = fileNameFormatFields.stream().map(f -> ValueUtils.getValueAsString(record.getValue(f))).toList();
fileName = QValueFormatter.formatStringWithValues(fileNameFormat, values);
}
}
@ -533,22 +542,7 @@ public class QValueFormatter
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// if field type is blob OR if there's a supplemental process or code-ref that needs to run - //
// then update its value to be a callback-url that'll give access to the bytes to download. //
// implied here is that a String value (w/o supplemental code/proc) has its value stay as a //
// URL, which is where the file is directly downloaded from. And in the case of a String //
// with code-to-run, then the code should run, followed by a redirect to the value URL. //
////////////////////////////////////////////////////////////////////////////////////////////////
if(QFieldType.BLOB.equals(field.getType())
|| adornmentValues.containsKey(AdornmentType.FileDownloadValues.SUPPLEMENTAL_CODE_REFERENCE)
|| adornmentValues.containsKey(AdornmentType.FileDownloadValues.SUPPLEMENTAL_PROCESS_NAME))
{
record.setValue(field.getName(), "/data/" + table.getName() + "/"
+ URLEncoder.encode(ValueUtils.getValueAsString(primaryKey), StandardCharsets.UTF_8) + "/"
+ field.getName() + "/"
+ URLEncoder.encode(Objects.requireNonNullElse(fileName, ""), StandardCharsets.UTF_8));
}
record.setValue(field.getName(), "/data/" + table.getName() + "/" + primaryKey + "/" + field.getName() + "/" + fileName);
record.setDisplayValue(field.getName(), fileName);
}
}

View File

@ -26,12 +26,8 @@ import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
@ -54,7 +50,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.StringUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
import org.apache.commons.lang.NotImplementedException;
/*******************************************************************************
@ -65,9 +61,6 @@ public class SearchPossibleValueSourceAction
{
private static final QLogger LOG = QLogger.getLogger(SearchPossibleValueSourceAction.class);
private static final Set<String> warnedAboutUnexpectedValueField = Collections.synchronizedSet(new HashSet<>());
private static final Set<String> warnedAboutUnexpectedNoOfFieldsToSearchByLabel = Collections.synchronizedSet(new HashSet<>());
private QPossibleValueTranslator possibleValueTranslator;
@ -117,7 +110,6 @@ public class SearchPossibleValueSourceAction
List<Serializable> matchingIds = new ArrayList<>();
List<?> inputIdsAsCorrectType = convertInputIdsToEnumIdType(possibleValueSource, input.getIdList());
Set<String> labels = null;
for(QPossibleValue<?> possibleValue : possibleValueSource.getEnumValues())
{
@ -130,24 +122,12 @@ public class SearchPossibleValueSourceAction
match = true;
}
}
else if(input.getLabelList() != null)
{
if(labels == null)
{
labels = input.getLabelList().stream().filter(Objects::nonNull).map(l -> l.toLowerCase()).collect(Collectors.toSet());
}
if(labels.contains(possibleValue.getLabel().toLowerCase()))
{
match = true;
}
}
else
{
if(StringUtils.hasContent(input.getSearchTerm()))
{
match = (Objects.equals(ValueUtils.getValueAsString(possibleValue.getId()).toLowerCase(), input.getSearchTerm().toLowerCase())
|| possibleValue.getLabel().toLowerCase().startsWith(input.getSearchTerm().toLowerCase()));
|| possibleValue.getLabel().toLowerCase().startsWith(input.getSearchTerm().toLowerCase()));
}
else
{
@ -188,37 +168,21 @@ public class SearchPossibleValueSourceAction
Object anIdFromTheEnum = possibleValueSource.getEnumValues().get(0).getId();
for(Serializable inputId : inputIdList)
if(anIdFromTheEnum instanceof Integer)
{
Object properlyTypedId = null;
try
{
if(anIdFromTheEnum instanceof Integer)
{
properlyTypedId = ValueUtils.getValueAsInteger(inputId);
}
else if(anIdFromTheEnum instanceof String)
{
properlyTypedId = ValueUtils.getValueAsString(inputId);
}
else if(anIdFromTheEnum instanceof Boolean)
{
properlyTypedId = ValueUtils.getValueAsBoolean(inputId);
}
else
{
LOG.warn("Unexpected type [" + anIdFromTheEnum.getClass().getSimpleName() + "] for ids in enum: " + possibleValueSource.getName());
}
}
catch(Exception e)
{
LOG.debug("Error converting possible value id to expected id type", e, logPair("value", inputId));
}
if (properlyTypedId != null)
{
rs.add(properlyTypedId);
}
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsInteger(id)));
}
else if(anIdFromTheEnum instanceof String)
{
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsString(id)));
}
else if(anIdFromTheEnum instanceof Boolean)
{
inputIdList.forEach(id -> rs.add(ValueUtils.getValueAsBoolean(id)));
}
else
{
LOG.warn("Unexpected type [" + anIdFromTheEnum.getClass().getSimpleName() + "] for ids in enum: " + possibleValueSource.getName());
}
return (rs);
@ -245,53 +209,6 @@ public class SearchPossibleValueSourceAction
{
queryFilter.addCriteria(new QFilterCriteria(table.getPrimaryKeyField(), QCriteriaOperator.IN, input.getIdList()));
}
else if(input.getLabelList() != null)
{
List<String> fieldNames = new ArrayList<>();
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// the 'value fields' will either be 'id' or 'label' (which means, use the fields from the tableMetaData's label fields) //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
for(String valueField : possibleValueSource.getValueFields())
{
if("id".equals(valueField))
{
fieldNames.add(table.getPrimaryKeyField());
}
else if("label".equals(valueField))
{
if(table.getRecordLabelFields() != null)
{
fieldNames.addAll(table.getRecordLabelFields());
}
}
else
{
String message = "Unexpected valueField defined in possibleValueSource when searching possibleValueSource by label (required: 'id' or 'label')";
if(!warnedAboutUnexpectedValueField.contains(possibleValueSource.getName()))
{
LOG.warn(message, logPair("valueField", valueField), logPair("possibleValueSource", possibleValueSource.getName()));
warnedAboutUnexpectedValueField.add(possibleValueSource.getName());
}
output.setWarning(message);
}
}
if(fieldNames.size() == 1)
{
queryFilter.addCriteria(new QFilterCriteria(fieldNames.get(0), QCriteriaOperator.IN, input.getLabelList()));
}
else
{
String message = "Unexpected number of fields found for searching possibleValueSource by label (required: 1, found: " + fieldNames.size() + ")";
if(!warnedAboutUnexpectedNoOfFieldsToSearchByLabel.contains(possibleValueSource.getName()))
{
LOG.warn(message);
warnedAboutUnexpectedNoOfFieldsToSearchByLabel.add(possibleValueSource.getName());
}
output.setWarning(message);
}
}
else
{
String searchTerm = input.getSearchTerm();
@ -352,8 +269,8 @@ public class SearchPossibleValueSourceAction
queryFilter = input.getDefaultQueryFilter();
}
queryFilter.setLimit(input.getLimit());
queryFilter.setSkip(input.getSkip());
// todo - skip & limit as params
queryFilter.setLimit(250);
queryFilter.setOrderBys(possibleValueSource.getOrderByFields());
@ -371,7 +288,7 @@ public class SearchPossibleValueSourceAction
fieldName = table.getPrimaryKeyField();
}
List<Serializable> ids = queryOutput.getRecords().stream().map(r -> r.getValue(fieldName)).toList();
List<Serializable> ids = queryOutput.getRecords().stream().map(r -> r.getValue(fieldName)).toList();
List<QPossibleValue<?>> qPossibleValues = possibleValueTranslator.buildTranslatedPossibleValueList(possibleValueSource, ids);
output.setResults(qPossibleValues);
@ -384,7 +301,7 @@ public class SearchPossibleValueSourceAction
**
*******************************************************************************/
@SuppressWarnings({ "rawtypes", "unchecked" })
private SearchPossibleValueSourceOutput searchPossibleValueCustom(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource) throws QException
private SearchPossibleValueSourceOutput searchPossibleValueCustom(SearchPossibleValueSourceInput input, QPossibleValueSource possibleValueSource)
{
try
{
@ -397,10 +314,11 @@ public class SearchPossibleValueSourceAction
}
catch(Exception e)
{
String message = "Error sending searching custom possible value source [" + input.getPossibleValueSourceName() + "]";
LOG.warn(message, e);
throw (new QException(message));
// LOG.warn("Error sending [" + value + "] for field [" + field + "] through custom code for PVS [" + field.getPossibleValueSourceName() + "]", e);
}
throw new NotImplementedException("Not impleemnted");
// return (null);
}
}

View File

@ -1,55 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
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.
*******************************************************************************/
public abstract class AbstractMetaDataProducerBasedQQQApplication extends AbstractQQQApplication
{
/***************************************************************************
**
***************************************************************************/
public abstract String getMetaDataPackageName();
/***************************************************************************
**
***************************************************************************/
@Override
public QInstance defineQInstance() throws QException
{
QInstance qInstance = new QInstance();
MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance, getMetaDataPackageName());
return (qInstance);
}
}

View File

@ -1,78 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException;
import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
** Base class to provide the definition of a QQQ-based application.
**
** Essentially, just how to define its meta-data - in the form of a QInstance.
**
** Also provides means to define the instance validation plugins to be used.
*******************************************************************************/
public abstract class AbstractQQQApplication
{
/***************************************************************************
**
***************************************************************************/
public abstract QInstance defineQInstance() throws QException;
/***************************************************************************
**
***************************************************************************/
public QInstance defineValidatedQInstance() throws QException, QInstanceValidationException
{
QInstance qInstance = defineQInstance();
QInstanceValidator.removeAllValidatorPlugins();
for(QInstanceValidatorPluginInterface<?> validatorPlugin : CollectionUtils.nonNullList(getValidatorPlugins()))
{
QInstanceValidator.addValidatorPlugin(validatorPlugin);
}
QInstanceValidator qInstanceValidator = new QInstanceValidator();
qInstanceValidator.validate(qInstance);
return (qInstance);
}
/***************************************************************************
**
***************************************************************************/
protected List<QInstanceValidatorPluginInterface<?>> getValidatorPlugins()
{
return new ArrayList<>();
}
}

View File

@ -23,11 +23,7 @@ package com.kingsrook.qqq.backend.core.instances;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -35,27 +31,21 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
import com.kingsrook.qqq.backend.core.actions.permissions.BulkTableActionProcessPermissionChecker;
import com.kingsrook.qqq.backend.core.actions.values.QCustomPossibleValueProvider;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.enrichment.plugins.QInstanceEnricherPluginInterface;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaDataInterface;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType.FileUploadAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DynamicDefaultValueBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldAdornment;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
@ -64,12 +54,10 @@ import com.kingsrook.qqq.backend.core.model.metadata.permissions.MetaDataWithPer
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStateMachineStep;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QSupplementalProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.reporting.QReportDataSource;
@ -86,11 +74,6 @@ import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEd
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEditTransformStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertExtractStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertLoadStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertPrepareFileMappingStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertPrepareFileUploadStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertPrepareValueMappingStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertReceiveFileMappingStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertReceiveValueMappingStep;
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertTransformStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
@ -98,7 +81,6 @@ import com.kingsrook.qqq.backend.core.scheduler.QScheduleManager;
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 static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
@ -124,8 +106,6 @@ public class QInstanceEnricher
//////////////////////////////////////////////////////////////////////////////////////////////////
private static final Map<String, String> labelMappings = new LinkedHashMap<>();
private static ListingHash<Class<?>, QInstanceEnricherPluginInterface<?>> enricherPlugins = new ListingHash<>();
/*******************************************************************************
@ -187,7 +167,6 @@ public class QInstanceEnricher
}
enrichJoins();
enrichInstance();
//////////////////////////////////////////////////////////////////////////////
// if the instance DOES have 1 or more scheduler, but no schedulable types, //
@ -204,16 +183,6 @@ public class QInstanceEnricher
/***************************************************************************
**
***************************************************************************/
private void enrichInstance()
{
runPlugins(QInstance.class, qInstance, qInstance);
}
/*******************************************************************************
**
*******************************************************************************/
@ -278,14 +247,6 @@ public class QInstanceEnricher
}
}
}
///////////////////////////////////////////
// run plugins on joins if there are any //
///////////////////////////////////////////
for(QJoinMetaData join : qInstance.getJoins().values())
{
runPlugins(QJoinMetaData.class, join, qInstance);
}
}
catch(Exception e)
{
@ -301,7 +262,6 @@ public class QInstanceEnricher
private void enrichWidget(QWidgetMetaDataInterface widgetMetaData)
{
enrichPermissionRules(widgetMetaData);
runPlugins(QWidgetMetaDataInterface.class, widgetMetaData, qInstance);
}
@ -312,7 +272,6 @@ public class QInstanceEnricher
private void enrichBackend(QBackendMetaData qBackendMetaData)
{
qBackendMetaData.enrich();
runPlugins(QBackendMetaData.class, qBackendMetaData, qInstance);
}
@ -353,7 +312,6 @@ public class QInstanceEnricher
enrichPermissionRules(table);
enrichAuditRules(table);
runPlugins(QTableMetaData.class, table, qInstance);
}
@ -444,7 +402,6 @@ public class QInstanceEnricher
}
enrichPermissionRules(process);
runPlugins(QProcessMetaData.class, process, qInstance);
}
@ -453,27 +410,10 @@ public class QInstanceEnricher
**
*******************************************************************************/
private void enrichStep(QStepMetaData step)
{
enrichStep(step, false);
}
/***************************************************************************
**
***************************************************************************/
private void enrichStep(QStepMetaData step, boolean isSubStep)
{
if(!StringUtils.hasContent(step.getLabel()))
{
if(isSubStep && (step.getName().endsWith(".backend") || step.getName().endsWith(".frontend")))
{
step.setLabel(nameToLabel(step.getName().replaceFirst("\\.(backend|frontend)", "")));
}
else
{
step.setLabel(nameToLabel(step.getName()));
}
step.setLabel(nameToLabel(step.getName()));
}
step.getInputFields().forEach(this::enrichField);
@ -494,13 +434,6 @@ public class QInstanceEnricher
frontendStepMetaData.getRecordListFields().forEach(this::enrichField);
}
}
else if(step instanceof QStateMachineStep stateMachineStep)
{
for(QStepMetaData subStep : CollectionUtils.nonNullList(stateMachineStep.getSubSteps()))
{
enrichStep(subStep, true);
}
}
}
@ -566,8 +499,6 @@ public class QInstanceEnricher
field.withBehavior(DynamicDefaultValueBehavior.MODIFY_DATE);
}
}
runPlugins(QFieldMetaData.class, field, qInstance);
}
@ -639,7 +570,6 @@ public class QInstanceEnricher
ensureAppSectionMembersAreAppChildren(app);
enrichPermissionRules(app);
runPlugins(QAppMetaData.class, app, qInstance);
}
@ -787,7 +717,6 @@ public class QInstanceEnricher
}
enrichPermissionRules(report);
runPlugins(QReportMetaData.class, report, qInstance);
}
@ -879,7 +808,7 @@ public class QInstanceEnricher
/*******************************************************************************
**
*******************************************************************************/
public void defineTableBulkInsert(QInstance qInstance, QTableMetaData table, String processName)
private void defineTableBulkInsert(QInstance qInstance, QTableMetaData table, String processName)
{
Map<String, Serializable> values = new HashMap<>();
values.put(StreamedETLWithFrontendProcess.FIELD_DESTINATION_TABLE, table.getName());
@ -891,7 +820,6 @@ public class QInstanceEnricher
values
)
.withName(processName)
.withIcon(new QIcon().withName("library_add"))
.withLabel(table.getLabel() + " Bulk Insert")
.withTableName(table.getName())
.withIsHidden(true)
@ -922,76 +850,18 @@ public class QInstanceEnricher
.map(QFieldMetaData::getLabel)
.collect(Collectors.joining(", "));
QBackendStepMetaData prepareFileUploadStep = new QBackendStepMetaData()
.withName("prepareFileUpload")
.withCode(new QCodeReference(BulkInsertPrepareFileUploadStep.class));
QFrontendStepMetaData uploadScreen = new QFrontendStepMetaData()
.withName("upload")
.withLabel("Upload File")
.withFormField(new QFieldMetaData("theFile", QFieldType.BLOB)
.withFieldAdornment(FileUploadAdornment.newFieldAdornment()
.withValue(FileUploadAdornment.formatDragAndDrop())
.withValue(FileUploadAdornment.widthFull()))
.withLabel(table.getLabel() + " File")
.withIsRequired(true))
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.HTML))
.withFormField(new QFieldMetaData("theFile", QFieldType.BLOB).withLabel(table.getLabel() + " File").withIsRequired(true))
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.HELP_TEXT)
.withValue("previewText", "file upload instructions")
.withValue("text", "Upload a CSV file with the following columns:\n" + fieldsForHelpText))
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.EDIT_FORM));
QBackendStepMetaData prepareFileMappingStep = new QBackendStepMetaData()
.withName("prepareFileMapping")
.withCode(new QCodeReference(BulkInsertPrepareFileMappingStep.class));
QFrontendStepMetaData fileMappingScreen = new QFrontendStepMetaData()
.withName("fileMapping")
.withLabel("File Mapping")
.withBackStepName("prepareFileUpload")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_FILE_MAPPING_FORM))
.withFormField(new QFieldMetaData("hasHeaderRow", QFieldType.BOOLEAN))
.withFormField(new QFieldMetaData("layout", QFieldType.STRING)); // is actually PVS, but, this field is only added to help support helpContent, so :shrug:
QBackendStepMetaData receiveFileMappingStep = new QBackendStepMetaData()
.withName("receiveFileMapping")
.withCode(new QCodeReference(BulkInsertReceiveFileMappingStep.class));
QBackendStepMetaData prepareValueMappingStep = new QBackendStepMetaData()
.withName("prepareValueMapping")
.withCode(new QCodeReference(BulkInsertPrepareValueMappingStep.class));
QFrontendStepMetaData valueMappingScreen = new QFrontendStepMetaData()
.withName("valueMapping")
.withLabel("Value Mapping")
.withBackStepName("prepareFileMapping")
.withComponent(new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_VALUE_MAPPING_FORM));
QBackendStepMetaData receiveValueMappingStep = new QBackendStepMetaData()
.withName("receiveValueMapping")
.withCode(new QCodeReference(BulkInsertReceiveValueMappingStep.class));
int i = 0;
process.addStep(i++, prepareFileUploadStep);
process.addStep(i++, uploadScreen);
process.addStep(i++, prepareFileMappingStep);
process.addStep(i++, fileMappingScreen);
process.addStep(i++, receiveFileMappingStep);
process.addStep(i++, prepareValueMappingStep);
process.addStep(i++, valueMappingScreen);
process.addStep(i++, receiveValueMappingStep);
process.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW).setRecordListFields(editableFields);
//////////////////////////////////////////////////////////////////////////////////////////
// put the bulk-load profile form (e.g., for saving it) on the review & result screens) //
//////////////////////////////////////////////////////////////////////////////////////////
process.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_REVIEW)
.withBackStepName("prepareFileMapping")
.getComponents().add(0, new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_PROFILE_FORM));
process.getFrontendStep(StreamedETLWithFrontendProcess.STEP_NAME_RESULT)
.getComponents().add(0, new QFrontendComponentMetaData().withType(QComponentType.BULK_LOAD_PROFILE_FORM));
process.addStep(0, uploadScreen);
process.getFrontendStep("review").setRecordListFields(editableFields);
qInstance.addProcess(process);
}
@ -1386,112 +1256,6 @@ public class QInstanceEnricher
}
}
if(possibleValueSource.getIdType() == null)
{
QTableMetaData table = qInstance.getTable(possibleValueSource.getTableName());
if(table != null)
{
String primaryKeyField = table.getPrimaryKeyField();
QFieldMetaData primaryKeyFieldMetaData = table.getFields().get(primaryKeyField);
if(primaryKeyFieldMetaData != null)
{
possibleValueSource.setIdType(primaryKeyFieldMetaData.getType());
}
}
}
}
else if(QPossibleValueSourceType.ENUM.equals(possibleValueSource.getType()))
{
if(possibleValueSource.getIdType() == null)
{
if(CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()))
{
Object id = possibleValueSource.getEnumValues().get(0).getId();
try
{
possibleValueSource.setIdType(QFieldType.fromClass(id.getClass()));
}
catch(Exception e)
{
LOG.warn("Error enriching possible value source with idType based on first enum value", e, logPair("possibleValueSource", possibleValueSource.getName()), logPair("id", id));
}
}
}
}
else if(QPossibleValueSourceType.CUSTOM.equals(possibleValueSource.getType()))
{
if(possibleValueSource.getIdType() == null)
{
try
{
QCustomPossibleValueProvider<?> customPossibleValueProvider = QCodeLoader.getCustomPossibleValueProvider(possibleValueSource);
Method getPossibleValueMethod = customPossibleValueProvider.getClass().getDeclaredMethod("getPossibleValue", Serializable.class);
Type returnType = getPossibleValueMethod.getGenericReturnType();
Type idType = ((ParameterizedType) returnType).getActualTypeArguments()[0];
if(idType instanceof Class<?> c)
{
possibleValueSource.setIdType(QFieldType.fromClass(c));
}
}
catch(Exception e)
{
LOG.warn("Error enriching possible value source with idType based on first custom value", e, logPair("possibleValueSource", possibleValueSource.getName()));
}
}
}
runPlugins(QPossibleValueSource.class, possibleValueSource, qInstance);
}
/*******************************************************************************
**
*******************************************************************************/
public static void addEnricherPlugin(QInstanceEnricherPluginInterface<?> plugin)
{
Optional<Method> enrichMethod = Arrays.stream(plugin.getClass().getDeclaredMethods())
.filter(m -> m.getName().equals("enrich")
&& m.getParameterCount() == 2
&& !m.getParameterTypes()[0].equals(Object.class)
&& m.getParameterTypes()[1].equals(QInstance.class)
).findFirst();
if(enrichMethod.isPresent())
{
Class<?> parameterType = enrichMethod.get().getParameterTypes()[0];
enricherPlugins.add(parameterType, plugin);
}
else
{
LOG.warn("Could not find enrich method on enricher plugin [" + plugin.getClass().getName() + "] (to infer type being enriched) - this plugin will not be used.");
}
}
/*******************************************************************************
**
*******************************************************************************/
public static void removeAllEnricherPlugins()
{
enricherPlugins.clear();
}
/*******************************************************************************
**
*******************************************************************************/
private <T> void runPlugins(Class<T> c, T t, QInstance qInstance)
{
for(QInstanceEnricherPluginInterface<?> plugin : CollectionUtils.nonNullList(enricherPlugins.get(c)))
{
@SuppressWarnings("unchecked")
QInstanceEnricherPluginInterface<T> castedPlugin = (QInstanceEnricherPluginInterface<T>) plugin;
castedPlugin.enrich(t, qInstance);
}
}

View File

@ -30,6 +30,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
@ -110,7 +111,7 @@ public class QInstanceHelpContentManager
}
else
{
LOG.info("Discarding help content with key-part that does not contain name:value format", logPair("key", key), logPair("part", part), logPair("id", record.getValue("id")));
LOG.info("Discarding help content with key that does not contain name:value format", logPair("key", key), logPair("id", record.getValue("id")));
}
}
@ -149,19 +150,19 @@ public class QInstanceHelpContentManager
///////////////////////////////////////////////////////////////////////////////////
if(StringUtils.hasContent(tableName))
{
processHelpContentForTable(qInstance, key, tableName, sectionName, fieldName, slotName, roles, helpContent);
processHelpContentForTable(key, tableName, sectionName, fieldName, slotName, roles, helpContent);
}
else if(StringUtils.hasContent(processName))
{
processHelpContentForProcess(qInstance, key, processName, fieldName, stepName, roles, helpContent);
processHelpContentForProcess(key, processName, fieldName, stepName, roles, helpContent);
}
else if(StringUtils.hasContent(widgetName))
{
processHelpContentForWidget(qInstance, key, widgetName, slotName, roles, helpContent);
processHelpContentForWidget(key, widgetName, slotName, roles, helpContent);
}
else if(nameValuePairs.containsKey("instanceLevel"))
{
processHelpContentForInstance(qInstance, key, slotName, roles, helpContent);
processHelpContentForInstance(key, slotName, roles, helpContent);
}
}
catch(Exception e)
@ -175,9 +176,9 @@ public class QInstanceHelpContentManager
/*******************************************************************************
**
*******************************************************************************/
private static void processHelpContentForTable(QInstance qInstance, String key, String tableName, String sectionName, String fieldName, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
private static void processHelpContentForTable(String key, String tableName, String sectionName, String fieldName, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
{
QTableMetaData table = qInstance.getTable(tableName);
QTableMetaData table = QContext.getQInstance().getTable(tableName);
if(table == null)
{
LOG.info("Unrecognized table in help content", logPair("key", key));
@ -245,30 +246,9 @@ public class QInstanceHelpContentManager
/*******************************************************************************
**
*******************************************************************************/
private static void processHelpContentForProcess(QInstance qInstance, String key, String processName, String fieldName, String stepName, Set<HelpRole> roles, QHelpContent helpContent)
private static void processHelpContentForProcess(String key, String processName, String fieldName, String stepName, Set<HelpRole> roles, QHelpContent helpContent)
{
if(processName.startsWith("*") && processName.length() > 1)
{
boolean anyMatched = false;
String subName = processName.substring(1);
for(QProcessMetaData process : qInstance.getProcesses().values())
{
if(process.getName().endsWith(subName))
{
anyMatched = true;
processHelpContentForProcess(qInstance, key, process.getName(), fieldName, stepName, roles, helpContent);
}
}
if(!anyMatched)
{
LOG.info("Wildcard process name did not match any processes in help content", logPair("key", key));
}
return;
}
QProcessMetaData process = qInstance.getProcess(processName);
QProcessMetaData process = QContext.getQInstance().getProcess(processName);
if(process == null)
{
LOG.info("Unrecognized process in help content", logPair("key", key));
@ -326,9 +306,9 @@ public class QInstanceHelpContentManager
/*******************************************************************************
**
*******************************************************************************/
private static void processHelpContentForWidget(QInstance qInstance, String key, String widgetName, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
private static void processHelpContentForWidget(String key, String widgetName, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
{
QWidgetMetaDataInterface widget = qInstance.getWidget(widgetName);
QWidgetMetaDataInterface widget = QContext.getQInstance().getWidget(widgetName);
if(!StringUtils.hasContent(slotName))
{
LOG.info("Missing slot name in help content", logPair("key", key));
@ -355,7 +335,7 @@ public class QInstanceHelpContentManager
/*******************************************************************************
**
*******************************************************************************/
private static void processHelpContentForInstance(QInstance qInstance, String key, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
private static void processHelpContentForInstance(String key, String slotName, Set<HelpRole> roles, QHelpContent helpContent)
{
if(!StringUtils.hasContent(slotName))
{
@ -365,11 +345,11 @@ public class QInstanceHelpContentManager
{
if(helpContent != null)
{
qInstance.withHelpContent(slotName, helpContent);
QContext.getQInstance().withHelpContent(slotName, helpContent);
}
else
{
qInstance.removeHelpContent(slotName, roles);
QContext.getQInstance().removeHelpContent(slotName, roles);
}
}
}

View File

@ -37,15 +37,12 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.kingsrook.qqq.backend.core.actions.automation.RecordAutomationHandler;
import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.AbstractWidgetRenderer;
import com.kingsrook.qqq.backend.core.actions.metadata.JoinGraph;
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;
import com.kingsrook.qqq.backend.core.actions.scripts.TestScriptActionInterface;
@ -72,13 +69,11 @@ import com.kingsrook.qqq.backend.core.model.metadata.fields.FieldBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.ValueTooLongBehavior;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSourceType;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QStepMetaData;
@ -110,16 +105,12 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.Automatio
import com.kingsrook.qqq.backend.core.model.metadata.tables.automation.QTableAutomationDetails;
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheOf;
import com.kingsrook.qqq.backend.core.model.metadata.tables.cache.CacheUseCase;
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantSetting;
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsConfig;
import com.kingsrook.qqq.backend.core.modules.authentication.QAuthenticationModuleCustomizerInterface;
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 com.kingsrook.qqq.backend.core.utils.ValueUtils;
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeFunction;
import com.kingsrook.qqq.backend.core.utils.lambdas.UnsafeLambda;
import org.apache.commons.lang.BooleanUtils;
import org.quartz.CronExpression;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -194,7 +185,6 @@ public class QInstanceValidator
//////////////////////////////////////////////////////////////////////////
try
{
validateInstanceAttributes(qInstance);
validateBackends(qInstance);
validateAuthentication(qInstance);
validateAutomationProviders(qInstance);
@ -235,19 +225,6 @@ public class QInstanceValidator
/***************************************************************************
**
***************************************************************************/
private void validateInstanceAttributes(QInstance qInstance)
{
if(qInstance.getMetaDataFilter() != null)
{
validateSimpleCodeReference("Instance metaDataFilter ", qInstance.getMetaDataFilter(), MetaDataFilterInterface.class);
}
}
/*******************************************************************************
**
*******************************************************************************/
@ -379,8 +356,8 @@ public class QInstanceValidator
assertCondition(join.getType() != null, "Missing type for join: " + joinName);
assertCondition(CollectionUtils.nullSafeHasContents(join.getJoinOns()), "Missing joinOns for join: " + joinName);
boolean leftTableExists = assertCondition(qInstance.getTable(join.getLeftTable()) != null, "Left-table name " + join.getLeftTable() + " in join " + joinName + " is not a defined table in this instance.");
boolean rightTableExists = assertCondition(qInstance.getTable(join.getRightTable()) != null, "Right-table name " + join.getRightTable() + " in join " + joinName + " is not a defined table in this instance.");
boolean leftTableExists = assertCondition(qInstance.getTable(join.getLeftTable()) != null, "Left-table name " + join.getLeftTable() + " join " + joinName + " is not a defined table in this instance.");
boolean rightTableExists = assertCondition(qInstance.getTable(join.getRightTable()) != null, "Right-table name " + join.getRightTable() + " join " + joinName + " is not a defined table in this instance.");
for(JoinOn joinOn : CollectionUtils.nonNullList(join.getJoinOns()))
{
@ -549,60 +526,6 @@ public class QInstanceValidator
{
assertCondition(Objects.equals(backendName, backend.getName()), "Inconsistent naming for backend: " + backendName + "/" + backend.getName() + ".");
///////////////////////
// validate variants //
///////////////////////
BackendVariantsConfig backendVariantsConfig = backend.getBackendVariantsConfig();
if(BooleanUtils.isTrue(backend.getUsesVariants()))
{
if(assertCondition(backendVariantsConfig != null, "Missing backendVariantsConfig in backend [" + backendName + "] which is marked as usesVariants"))
{
assertCondition(StringUtils.hasContent(backendVariantsConfig.getVariantTypeKey()), "Missing variantTypeKey in backendVariantsConfig in [" + backendName + "]");
String optionsTableName = backendVariantsConfig.getOptionsTableName();
QTableMetaData optionsTable = qInstance.getTable(optionsTableName);
if(assertCondition(StringUtils.hasContent(optionsTableName), "Missing optionsTableName in backendVariantsConfig in [" + backendName + "]"))
{
if(assertCondition(optionsTable != null, "Unrecognized optionsTableName [" + optionsTableName + "] in backendVariantsConfig in [" + backendName + "]"))
{
QQueryFilter optionsFilter = backendVariantsConfig.getOptionsFilter();
if(optionsFilter != null)
{
validateQueryFilter(qInstance, "optionsFilter in backendVariantsConfig in backend [" + backendName + "]: ", optionsTable, optionsFilter, null);
}
}
}
Map<BackendVariantSetting, String> backendSettingSourceFieldNameMap = backendVariantsConfig.getBackendSettingSourceFieldNameMap();
if(assertCondition(CollectionUtils.nullSafeHasContents(backendSettingSourceFieldNameMap), "Missing or empty backendSettingSourceFieldNameMap in backendVariantsConfig in [" + backendName + "]"))
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// only validate field names in the backendSettingSourceFieldNameMap if there is NOT a variantRecordSupplier //
// (the idea being, that the supplier might be building a record with fieldNames that aren't in the table... //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(optionsTable != null && backendVariantsConfig.getVariantRecordLookupFunction() == null)
{
for(Map.Entry<BackendVariantSetting, String> entry : backendSettingSourceFieldNameMap.entrySet())
{
assertCondition(optionsTable.getFields().containsKey(entry.getValue()), "Unrecognized fieldName [" + entry.getValue() + "] in backendSettingSourceFieldNameMap in backendVariantsConfig in [" + backendName + "]");
}
}
}
if(backendVariantsConfig.getVariantRecordLookupFunction() != null)
{
validateSimpleCodeReference("VariantRecordSupplier in backendVariantsConfig in backend [" + backendName + "]: ", backendVariantsConfig.getVariantRecordLookupFunction(), UnsafeFunction.class, Function.class);
}
}
}
else
{
assertCondition(backendVariantsConfig == null, "Should not have a backendVariantsConfig in backend [" + backendName + "] which is not marked as usesVariants");
}
///////////////////////////////////////////
// let the backend do its own validation //
///////////////////////////////////////////
backend.performValidation(this);
runPlugins(QBackendMetaData.class, backend, qInstance);
@ -637,7 +560,7 @@ public class QInstanceValidator
private void validateAuthentication(QInstance qInstance)
{
QAuthenticationMetaData authentication = qInstance.getAuthentication();
if(assertCondition(authentication != null, "Authentication MetaData must be defined."))
if(authentication != null)
{
if(authentication.getCustomizer() != null)
{
@ -840,38 +763,15 @@ public class QInstanceValidator
{
if(assertCondition(StringUtils.hasContent(association.getName()), "missing a name for an Association on table " + table.getName()))
{
String messageSuffix = " for Association " + association.getName() + " on table " + table.getName();
boolean recognizedTable = false;
String messageSuffix = " for Association " + association.getName() + " on table " + table.getName();
if(assertCondition(StringUtils.hasContent(association.getAssociatedTableName()), "missing associatedTableName" + messageSuffix))
{
if(assertCondition(qInstance.getTable(association.getAssociatedTableName()) != null, "unrecognized associatedTableName " + association.getAssociatedTableName() + messageSuffix))
{
recognizedTable = true;
}
assertCondition(qInstance.getTable(association.getAssociatedTableName()) != null, "unrecognized associatedTableName " + association.getAssociatedTableName() + messageSuffix);
}
if(assertCondition(StringUtils.hasContent(association.getJoinName()), "missing joinName" + messageSuffix))
{
QJoinMetaData join = qInstance.getJoin(association.getJoinName());
if(assertCondition(join != null, "unrecognized joinName " + association.getJoinName() + messageSuffix))
{
assert join != null; // covered by the assertCondition
if(recognizedTable)
{
boolean isLeftToRight = join.getLeftTable().equals(table.getName()) && join.getRightTable().equals(association.getAssociatedTableName());
boolean isRightToLeft = join.getRightTable().equals(table.getName()) && join.getLeftTable().equals(association.getAssociatedTableName());
assertCondition(isLeftToRight || isRightToLeft, "join [" + association.getJoinName() + "] does not connect tables [" + table.getName() + "] and [" + association.getAssociatedTableName() + "]" + messageSuffix);
if(isLeftToRight)
{
assertCondition(join.getType().equals(JoinType.ONE_TO_MANY) || join.getType().equals(JoinType.ONE_TO_ONE), "Join type does not have 'one' on this table's side side (left)" + messageSuffix);
}
else if(isRightToLeft)
{
assertCondition(join.getType().equals(JoinType.MANY_TO_ONE) || join.getType().equals(JoinType.ONE_TO_ONE), "Join type does not have 'one' on this table's side (right)" + messageSuffix);
}
}
}
assertCondition(qInstance.getJoin(association.getJoinName()) != null, "unrecognized joinName " + association.getJoinName() + messageSuffix);
}
}
}
@ -1028,8 +928,13 @@ public class QInstanceValidator
assertCondition(Objects.equals(fieldName, field.getName()),
"Inconsistent naming in table " + tableName + " for field " + fieldName + "/" + field.getName() + ".");
if(field.getPossibleValueSourceName() != null)
{
assertCondition(qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null,
"Unrecognized possibleValueSourceName " + field.getPossibleValueSourceName() + " in table " + tableName + " for field " + fieldName + ".");
}
String prefix = "Field " + fieldName + " in table " + tableName + " ";
validateFieldPossibleValueSourceAttributes(qInstance, field, prefix);
///////////////////////////////////////////////////
// validate things we know about field behaviors //
@ -1048,15 +953,7 @@ public class QInstanceValidator
@SuppressWarnings("unchecked")
Class<FieldBehavior<?>> behaviorClass = (Class<FieldBehavior<?>>) fieldBehavior.getClass();
List<String> behaviorErrors = fieldBehavior.validateBehaviorConfiguration(table, field);
if(behaviorErrors != null)
{
String prefixMinusTrailingSpace = prefix.replaceFirst(" *$", "");
for(String behaviorError : behaviorErrors)
{
errors.add(prefixMinusTrailingSpace + ": " + behaviorClass.getSimpleName() + ": " + behaviorError);
}
}
errors.addAll(fieldBehavior.validateBehaviorConfiguration(table, field));
if(!fieldBehavior.allowMultipleBehaviorsOfThisType())
{
@ -1142,31 +1039,6 @@ public class QInstanceValidator
/***************************************************************************
**
***************************************************************************/
private void validateFieldPossibleValueSourceAttributes(QInstance qInstance, QFieldMetaData field, String prefix)
{
if(field.getPossibleValueSourceName() != null)
{
assertCondition(qInstance.getPossibleValueSource(field.getPossibleValueSourceName()) != null,
prefix + "has an unrecognized possibleValueSourceName " + field.getPossibleValueSourceName());
assertCondition(field.getInlinePossibleValueSource() == null, prefix.trim() + " has both a possibleValueSourceName and an inlinePossibleValueSource, which is not allowed.");
}
if(field.getInlinePossibleValueSource() != null)
{
String name = "inlinePossibleValueSource for " + prefix.trim();
if(assertCondition(QPossibleValueSourceType.ENUM.equals(field.getInlinePossibleValueSource().getType()), name + " must have a type of ENUM."))
{
validatePossibleValueSource(qInstance, name, field.getInlinePossibleValueSource());
}
}
}
/*******************************************************************************
**
*******************************************************************************/
@ -1416,7 +1288,7 @@ public class QInstanceValidator
////////////////////////////////////////////////////////////////////////
if(customizerInstance != null && tableCustomizer.getExpectedType() != null)
{
assertObjectCanBeCasted(prefix, customizerInstance, tableCustomizer.getExpectedType());
assertObjectCanBeCasted(prefix, tableCustomizer.getExpectedType(), customizerInstance);
}
}
}
@ -1428,31 +1300,18 @@ public class QInstanceValidator
/*******************************************************************************
** Make sure that a given object can be casted to an expected type.
*******************************************************************************/
private void assertObjectCanBeCasted(String errorPrefix, Object object, Class<?>... anyOfExpectedClasses)
private <T> T assertObjectCanBeCasted(String errorPrefix, Class<T> expectedType, Object object)
{
for(Class<?> expectedClass : anyOfExpectedClasses)
T castedObject = null;
try
{
try
{
expectedClass.cast(object);
return;
}
catch(ClassCastException e)
{
/////////////////////////////////////
// try next type (if there is one) //
/////////////////////////////////////
}
castedObject = expectedType.cast(object);
}
if(anyOfExpectedClasses.length == 1)
catch(ClassCastException e)
{
errors.add(errorPrefix + "CodeReference is not of the expected type: " + anyOfExpectedClasses[0]);
}
else
{
errors.add(errorPrefix + "CodeReference is not any of the expected types: " + Arrays.stream(anyOfExpectedClasses).map(c -> c.getName()).collect(Collectors.joining(", ")));
errors.add(errorPrefix + "CodeReference is not of the expected type: " + expectedType);
}
return castedObject;
}
@ -1687,16 +1546,6 @@ public class QInstanceValidator
}
}
for(QFieldMetaData field : process.getInputFields())
{
validateFieldPossibleValueSourceAttributes(qInstance, field, "Process " + processName + ", input field " + field.getName() + " ");
}
for(QFieldMetaData field : process.getOutputFields())
{
validateFieldPossibleValueSourceAttributes(qInstance, field, "Process " + processName + ", output field " + field.getName() + " ");
}
if(process.getCancelStep() != null)
{
if(assertCondition(process.getCancelStep().getCode() != null, "Cancel step is missing a code reference, in process " + processName))
@ -2081,11 +1930,6 @@ public class QInstanceValidator
}
}
if(widget.getValidatorPlugin() != null)
{
widget.getValidatorPlugin().validate(widget, qInstance, this);
}
runPlugins(QWidgetMetaDataInterface.class, widget, qInstance);
}
);
@ -2104,100 +1948,87 @@ public class QInstanceValidator
qInstance.getPossibleValueSources().forEach((pvsName, possibleValueSource) ->
{
assertCondition(Objects.equals(pvsName, possibleValueSource.getName()), "Inconsistent naming for possibleValueSource: " + pvsName + "/" + possibleValueSource.getName() + ".");
validatePossibleValueSource(qInstance, pvsName, possibleValueSource);
if(assertCondition(possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + pvsName))
{
////////////////////////////////////////////////////////////////////////////////////////////////
// assert about fields that should and should not be set, based on possible value source type //
// do additional type-specific validations as well //
////////////////////////////////////////////////////////////////////////////////////////////////
switch(possibleValueSource.getType())
{
case ENUM ->
{
assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + pvsName + " should not have a tableName.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "enum-type possibleValueSource " + pvsName + " should not have searchFields.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "enum-type possibleValueSource " + pvsName + " should not have orderByFields.");
assertCondition(possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + pvsName + " is missing enum values");
}
case TABLE ->
{
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + pvsName + " should not have enum values.");
assertCondition(possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + pvsName + " should not have a customCodeReference.");
QTableMetaData tableMetaData = null;
if(assertCondition(StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + pvsName + " is missing a tableName."))
{
tableMetaData = qInstance.getTable(possibleValueSource.getTableName());
assertCondition(tableMetaData != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + pvsName + ".");
}
if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "table-type possibleValueSource " + pvsName + " is missing searchFields."))
{
if(tableMetaData != null)
{
QTableMetaData finalTableMetaData = tableMetaData;
for(String searchField : possibleValueSource.getSearchFields())
{
assertNoException(() -> finalTableMetaData.getField(searchField), "possibleValueSource " + pvsName + " has an unrecognized searchField: " + searchField);
}
}
}
if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "table-type possibleValueSource " + pvsName + " is missing orderByFields."))
{
if(tableMetaData != null)
{
QTableMetaData finalTableMetaData = tableMetaData;
for(QFilterOrderBy orderByField : possibleValueSource.getOrderByFields())
{
assertNoException(() -> finalTableMetaData.getField(orderByField.getFieldName()), "possibleValueSource " + pvsName + " has an unrecognized orderByField: " + orderByField.getFieldName());
}
}
}
}
case CUSTOM ->
{
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + pvsName + " should not have enum values.");
assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + pvsName + " should not have a tableName.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "custom-type possibleValueSource " + pvsName + " should not have searchFields.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "custom-type possibleValueSource " + pvsName + " should not have orderByFields.");
if(assertCondition(possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + pvsName + " is missing a customCodeReference."))
{
validateSimpleCodeReference("PossibleValueSource " + pvsName + " custom code reference: ", possibleValueSource.getCustomCodeReference(), QCustomPossibleValueProvider.class);
}
}
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
}
runPlugins(QPossibleValueSource.class, possibleValueSource, qInstance);
}
});
}
}
/***************************************************************************
**
***************************************************************************/
private void validatePossibleValueSource(QInstance qInstance, String name, QPossibleValueSource possibleValueSource)
{
if(assertCondition(possibleValueSource.getType() != null, "Missing type for possibleValueSource: " + name))
{
////////////////////////////////////////////////////////////////////////////////////////////////
// assert about fields that should and should not be set, based on possible value source type //
// do additional type-specific validations as well //
////////////////////////////////////////////////////////////////////////////////////////////////
switch(possibleValueSource.getType())
{
case ENUM ->
{
assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "enum-type possibleValueSource " + name + " should not have a tableName.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "enum-type possibleValueSource " + name + " should not have searchFields.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "enum-type possibleValueSource " + name + " should not have orderByFields.");
assertCondition(possibleValueSource.getCustomCodeReference() == null, "enum-type possibleValueSource " + name + " should not have a customCodeReference.");
assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getEnumValues()), "enum-type possibleValueSource " + name + " is missing enum values");
}
case TABLE ->
{
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "table-type possibleValueSource " + name + " should not have enum values.");
assertCondition(possibleValueSource.getCustomCodeReference() == null, "table-type possibleValueSource " + name + " should not have a customCodeReference.");
QTableMetaData tableMetaData = null;
if(assertCondition(StringUtils.hasContent(possibleValueSource.getTableName()), "table-type possibleValueSource " + name + " is missing a tableName."))
{
tableMetaData = qInstance.getTable(possibleValueSource.getTableName());
assertCondition(tableMetaData != null, "Unrecognized table " + possibleValueSource.getTableName() + " for possibleValueSource " + name + ".");
}
if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "table-type possibleValueSource " + name + " is missing searchFields."))
{
if(tableMetaData != null)
{
QTableMetaData finalTableMetaData = tableMetaData;
for(String searchField : possibleValueSource.getSearchFields())
{
assertNoException(() -> finalTableMetaData.getField(searchField), "possibleValueSource " + name + " has an unrecognized searchField: " + searchField);
}
}
}
if(assertCondition(CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "table-type possibleValueSource " + name + " is missing orderByFields."))
{
if(tableMetaData != null)
{
QTableMetaData finalTableMetaData = tableMetaData;
for(QFilterOrderBy orderByField : possibleValueSource.getOrderByFields())
{
assertNoException(() -> finalTableMetaData.getField(orderByField.getFieldName()), "possibleValueSource " + name + " has an unrecognized orderByField: " + orderByField.getFieldName());
}
}
}
}
case CUSTOM ->
{
assertCondition(CollectionUtils.nullSafeIsEmpty(possibleValueSource.getEnumValues()), "custom-type possibleValueSource " + name + " should not have enum values.");
assertCondition(!StringUtils.hasContent(possibleValueSource.getTableName()), "custom-type possibleValueSource " + name + " should not have a tableName.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getSearchFields()), "custom-type possibleValueSource " + name + " should not have searchFields.");
assertCondition(!CollectionUtils.nullSafeHasContents(possibleValueSource.getOrderByFields()), "custom-type possibleValueSource " + name + " should not have orderByFields.");
if(assertCondition(possibleValueSource.getCustomCodeReference() != null, "custom-type possibleValueSource " + name + " is missing a customCodeReference."))
{
validateSimpleCodeReference("PossibleValueSource " + name + " custom code reference: ", possibleValueSource.getCustomCodeReference(), QCustomPossibleValueProvider.class);
}
}
default -> errors.add("Unexpected possibleValueSource type: " + possibleValueSource.getType());
}
assertCondition(possibleValueSource.getIdType() != null, "possibleValueSource " + name + " is missing its idType.");
runPlugins(QPossibleValueSource.class, possibleValueSource, qInstance);
}
}
/*******************************************************************************
**
*******************************************************************************/
@SafeVarargs
private void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class<?>... anyOfExpectedClasses)
private void validateSimpleCodeReference(String prefix, QCodeReference codeReference, Class<?> expectedClass)
{
if(!preAssertionsForCodeReference(codeReference, prefix))
{
@ -2225,7 +2056,7 @@ public class QInstanceValidator
////////////////////////////////////////////////////////////////////////
if(classInstance != null)
{
assertObjectCanBeCasted(prefix, classInstance, anyOfExpectedClasses);
assertObjectCanBeCasted(prefix, expectedClass, classInstance);
}
}
}

View File

@ -1,40 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.instances.enrichment.plugins;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Interface for additional / optional enrichment to be done on q instance members.
** Some may be provided by QQQ - others can be defined by applications.
*******************************************************************************/
public interface QInstanceEnricherPluginInterface<T>
{
/*******************************************************************************
**
*******************************************************************************/
void enrich(T object, QInstance qInstance);
}

View File

@ -1,6 +1,6 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* Copyright (C) 2021-2023. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
@ -19,20 +19,22 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata;
package com.kingsrook.qqq.backend.core.model;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
/*******************************************************************************
** Interface for classes that know how to produce meta data objects. Useful with
** MetaDataProducerHelper, to point at a package full of these, and populate
** MetaDataProducerHelper, to put point at a package full of these, and populate
** your whole QInstance.
**
** See also MetaDataProducer - an implementer of this interface, which actually
** came first, and is fine to extend if producing a meta-data class is all your
** class means to do (nice and "Single-responsibility principle").
** clas means to do (nice and "Single-responsibility principle").
**
** But, in some applications you may want to, for example, have one class that
** defines a process step, and also produces the meta-data for that process, so

View File

@ -326,20 +326,6 @@ public class AuditSingleInput implements Serializable
/*******************************************************************************
** Fluent setter for details
*******************************************************************************/
public AuditSingleInput withDetailMessages(List<String> details)
{
for(String detail : details)
{
addDetail(message);
}
return (this);
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -31,16 +31,6 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
*******************************************************************************/
public class MetaDataInput extends AbstractActionInput
{
private String frontendName;
private String frontendVersion;
private String middlewareName;
private String middlewareVersion;
private String applicationName;
private String applicationVersion;
/*******************************************************************************
**
@ -49,190 +39,4 @@ public class MetaDataInput extends AbstractActionInput
{
}
/*******************************************************************************
** Getter for frontendName
*******************************************************************************/
public String getFrontendName()
{
return (this.frontendName);
}
/*******************************************************************************
** Setter for frontendName
*******************************************************************************/
public void setFrontendName(String frontendName)
{
this.frontendName = frontendName;
}
/*******************************************************************************
** Fluent setter for frontendName
*******************************************************************************/
public MetaDataInput withFrontendName(String frontendName)
{
this.frontendName = frontendName;
return (this);
}
/*******************************************************************************
** Getter for frontendVersion
*******************************************************************************/
public String getFrontendVersion()
{
return (this.frontendVersion);
}
/*******************************************************************************
** Setter for frontendVersion
*******************************************************************************/
public void setFrontendVersion(String frontendVersion)
{
this.frontendVersion = frontendVersion;
}
/*******************************************************************************
** Fluent setter for frontendVersion
*******************************************************************************/
public MetaDataInput withFrontendVersion(String frontendVersion)
{
this.frontendVersion = frontendVersion;
return (this);
}
/*******************************************************************************
** Getter for middlewareName
*******************************************************************************/
public String getMiddlewareName()
{
return (this.middlewareName);
}
/*******************************************************************************
** Setter for middlewareName
*******************************************************************************/
public void setMiddlewareName(String middlewareName)
{
this.middlewareName = middlewareName;
}
/*******************************************************************************
** Fluent setter for middlewareName
*******************************************************************************/
public MetaDataInput withMiddlewareName(String middlewareName)
{
this.middlewareName = middlewareName;
return (this);
}
/*******************************************************************************
** Getter for middlewareVersion
*******************************************************************************/
public String getMiddlewareVersion()
{
return (this.middlewareVersion);
}
/*******************************************************************************
** Setter for middlewareVersion
*******************************************************************************/
public void setMiddlewareVersion(String middlewareVersion)
{
this.middlewareVersion = middlewareVersion;
}
/*******************************************************************************
** Fluent setter for middlewareVersion
*******************************************************************************/
public MetaDataInput withMiddlewareVersion(String middlewareVersion)
{
this.middlewareVersion = middlewareVersion;
return (this);
}
/*******************************************************************************
** Getter for applicationName
*******************************************************************************/
public String getApplicationName()
{
return (this.applicationName);
}
/*******************************************************************************
** Setter for applicationName
*******************************************************************************/
public void setApplicationName(String applicationName)
{
this.applicationName = applicationName;
}
/*******************************************************************************
** Fluent setter for applicationName
*******************************************************************************/
public MetaDataInput withApplicationName(String applicationName)
{
this.applicationName = applicationName;
return (this);
}
/*******************************************************************************
** Getter for applicationVersion
*******************************************************************************/
public String getApplicationVersion()
{
return (this.applicationVersion);
}
/*******************************************************************************
** Setter for applicationVersion
*******************************************************************************/
public void setApplicationVersion(String applicationVersion)
{
this.applicationVersion = applicationVersion;
}
/*******************************************************************************
** Fluent setter for applicationVersion
*******************************************************************************/
public MetaDataInput withApplicationVersion(String applicationVersion)
{
this.applicationVersion = applicationVersion;
return (this);
}
}

View File

@ -1,139 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
** Object that stores adjustments that a process wants to make, at run-time,
** to its meta-data.
**
** e.g., changing the steps; updating fields (e.g., changing an inline PVS,
** or an isRequired attribute)
*******************************************************************************/
public class ProcessMetaDataAdjustment
{
private static final QLogger LOG = QLogger.getLogger(ProcessMetaDataAdjustment.class);
private List<QFrontendStepMetaData> updatedFrontendStepList = null;
private Map<String, QFieldMetaData> updatedFields = null;
/*******************************************************************************
**
*******************************************************************************/
public ProcessMetaDataAdjustment withUpdatedField(QFieldMetaData field)
{
if(updatedFields == null)
{
updatedFields = new LinkedHashMap<>();
}
if(!StringUtils.hasContent(field.getName()))
{
LOG.warn("Missing name on field in withUpdatedField - no update will happen.");
}
else
{
if(updatedFields.containsKey(field.getName()))
{
LOG.info("UpdatedFields map already contained a field with this name - overwriting it.", logPair("fieldName", field.getName()));
}
updatedFields.put(field.getName(), field);
}
return (this);
}
/*******************************************************************************
** Getter for updatedFrontendStepList
*******************************************************************************/
public List<QFrontendStepMetaData> getUpdatedFrontendStepList()
{
return (this.updatedFrontendStepList);
}
/*******************************************************************************
** Setter for updatedFrontendStepList
*******************************************************************************/
public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
{
this.updatedFrontendStepList = updatedFrontendStepList;
}
/*******************************************************************************
** Fluent setter for updatedFrontendStepList
*******************************************************************************/
public ProcessMetaDataAdjustment withUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
{
this.updatedFrontendStepList = updatedFrontendStepList;
return (this);
}
/*******************************************************************************
** Getter for updatedFields
*******************************************************************************/
public Map<String, QFieldMetaData> getUpdatedFields()
{
return (this.updatedFields);
}
/*******************************************************************************
** Setter for updatedFields
*******************************************************************************/
public void setUpdatedFields(Map<String, QFieldMetaData> updatedFields)
{
this.updatedFields = updatedFields;
}
/*******************************************************************************
** Fluent setter for updatedFields
*******************************************************************************/
public ProcessMetaDataAdjustment withUpdatedFields(Map<String, QFieldMetaData> updatedFields)
{
this.updatedFields = updatedFields;
return (this);
}
}

View File

@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
/*******************************************************************************
@ -40,10 +41,11 @@ public class ProcessState implements Serializable
private Map<String, Serializable> values = new HashMap<>();
private List<String> stepList = new ArrayList<>();
private Optional<String> nextStepName = Optional.empty();
private Optional<String> backStepName = Optional.empty();
private boolean isStepBack = false;
private ProcessMetaDataAdjustment processMetaDataAdjustment = null;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// maybe, remove this altogether - just let the frontend compute & send if needed... but how does it know last version...? //
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private List<QFrontendStepMetaData> updatedFrontendStepList = null;
@ -124,39 +126,6 @@ public class ProcessState implements Serializable
/*******************************************************************************
** Getter for backStepName
**
*******************************************************************************/
public Optional<String> getBackStepName()
{
return backStepName;
}
/*******************************************************************************
** Setter for backStepName
**
*******************************************************************************/
public void setBackStepName(String backStepName)
{
this.backStepName = Optional.of(backStepName);
}
/*******************************************************************************
** clear out the value of backStepName (set the Optional to empty)
**
*******************************************************************************/
public void clearBackStepName()
{
this.backStepName = Optional.empty();
}
/*******************************************************************************
** Getter for stepList
**
@ -179,67 +148,33 @@ public class ProcessState implements Serializable
/*******************************************************************************
** Getter for processMetaDataAdjustment
** Getter for updatedFrontendStepList
*******************************************************************************/
public ProcessMetaDataAdjustment getProcessMetaDataAdjustment()
public List<QFrontendStepMetaData> getUpdatedFrontendStepList()
{
return (this.processMetaDataAdjustment);
return (this.updatedFrontendStepList);
}
/*******************************************************************************
** Setter for processMetaDataAdjustment
** Setter for updatedFrontendStepList
*******************************************************************************/
public void setProcessMetaDataAdjustment(ProcessMetaDataAdjustment processMetaDataAdjustment)
public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
{
this.processMetaDataAdjustment = processMetaDataAdjustment;
this.updatedFrontendStepList = updatedFrontendStepList;
}
/*******************************************************************************
** Fluent setter for processMetaDataAdjustment
** Fluent setter for updatedFrontendStepList
*******************************************************************************/
public ProcessState withProcessMetaDataAdjustment(ProcessMetaDataAdjustment processMetaDataAdjustment)
public ProcessState withUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
{
this.processMetaDataAdjustment = processMetaDataAdjustment;
this.updatedFrontendStepList = updatedFrontendStepList;
return (this);
}
/*******************************************************************************
** Getter for isStepBack
*******************************************************************************/
public boolean getIsStepBack()
{
return (this.isStepBack);
}
/*******************************************************************************
** Setter for isStepBack
*******************************************************************************/
public void setIsStepBack(boolean isStepBack)
{
this.isStepBack = isStepBack;
}
/*******************************************************************************
** Fluent setter for isStepBack
*******************************************************************************/
public ProcessState withIsStepBack(boolean isStepBack)
{
this.isStepBack = isStepBack;
return (this);
}
}

View File

@ -53,7 +53,6 @@ public class ProcessSummaryLine implements ProcessSummaryLineInterface
//////////////////////////////////////////////////////////////////////////
private ArrayList<Serializable> primaryKeys;
private ArrayList<String> bulletsOfText;
/*******************************************************************************
@ -498,35 +497,4 @@ public class ProcessSummaryLine implements ProcessSummaryLineInterface
return (this);
}
/*******************************************************************************
** Getter for bulletsOfText
*******************************************************************************/
public ArrayList<String> getBulletsOfText()
{
return (this.bulletsOfText);
}
/*******************************************************************************
** Setter for bulletsOfText
*******************************************************************************/
public void setBulletsOfText(ArrayList<String> bulletsOfText)
{
this.bulletsOfText = bulletsOfText;
}
/*******************************************************************************
** Fluent setter for bulletsOfText
*******************************************************************************/
public ProcessSummaryLine withBulletsOfText(ArrayList<String> bulletsOfText)
{
this.bulletsOfText = bulletsOfText;
return (this);
}
}

View File

@ -25,27 +25,19 @@ package com.kingsrook.qqq.backend.core.model.actions.processes;
import java.io.Serializable;
import java.time.Instant;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobCallback;
import com.kingsrook.qqq.backend.core.actions.async.AsyncJobStatus;
import com.kingsrook.qqq.backend.core.actions.async.NonPersistedAsyncJobCallback;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
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.processes.tracing.ProcessTracerInterface;
import com.kingsrook.qqq.backend.core.processes.tracing.ProcessTracerMessage;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
@ -54,8 +46,6 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
*******************************************************************************/
public class RunBackendStepInput extends AbstractActionInput
{
private static final QLogger LOG = QLogger.getLogger(RunBackendStepInput.class);
private ProcessState processState;
private String processName;
private String tableName;
@ -65,13 +55,12 @@ public class RunBackendStepInput extends AbstractActionInput
private RunProcessInput.FrontendStepBehavior frontendStepBehavior;
private Instant basepullLastRunTime;
private ProcessTracerInterface processTracer;
////////////////////////////////////////////////////////////////////////////
// note - new fields should generally be added in method: cloneFieldsInto //
////////////////////////////////////////////////////////////////////////////
/*******************************************************************************
**
*******************************************************************************/
@ -107,7 +96,6 @@ public class RunBackendStepInput extends AbstractActionInput
target.setAsyncJobCallback(getAsyncJobCallback());
target.setFrontendStepBehavior(getFrontendStepBehavior());
target.setValues(getValues());
target.setProcessTracer(getProcessTracer().orElse(null));
}
@ -250,26 +238,6 @@ public class RunBackendStepInput extends AbstractActionInput
/*******************************************************************************
** Getter for records converted to entities of a given type.
**
*******************************************************************************/
public <E extends QRecordEntity> List<E> getRecordsAsEntities(Class<E> entityClass) throws QException
{
List<E> rs = new ArrayList<>();
///////////////////////////////////////////////////////////////////////////////////
// note - important to call getRecords here, which is overwritten in subclasses! //
///////////////////////////////////////////////////////////////////////////////////
for(QRecord record : getRecords())
{
rs.add(QRecordEntity.fromQRecord(entityClass, record));
}
return (rs);
}
/*******************************************************************************
** Setter for records
**
@ -451,17 +419,6 @@ public class RunBackendStepInput extends AbstractActionInput
/*******************************************************************************
** Accessor for processState's isStepBack attribute
**
*******************************************************************************/
public boolean getIsStepBack()
{
return processState.getIsStepBack();
}
/*******************************************************************************
** Accessor for processState - protected, because we generally want to access
** its members through wrapper methods, we think
@ -567,54 +524,4 @@ public class RunBackendStepInput extends AbstractActionInput
return (this);
}
/*******************************************************************************
** Setter for processTracer
*******************************************************************************/
public void setProcessTracer(ProcessTracerInterface processTracer)
{
this.processTracer = processTracer;
}
/*******************************************************************************
** Fluent setter for processTracer
*******************************************************************************/
public RunBackendStepInput withProcessTracer(ProcessTracerInterface processTracer)
{
this.processTracer = processTracer;
return (this);
}
/***************************************************************************
**
***************************************************************************/
public Optional<ProcessTracerInterface> getProcessTracer()
{
return Optional.ofNullable(processTracer);
}
/***************************************************************************
**
***************************************************************************/
public void traceMessage(ProcessTracerMessage message)
{
if(processTracer != null && message != null)
{
try
{
processTracer.handleMessage(this, message);
}
catch(Exception e)
{
LOG.warn("Error tracing message", e, logPair("message", message));
}
}
}
}

View File

@ -33,7 +33,6 @@ import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.actions.audits.AuditInput;
import com.kingsrook.qqq.backend.core.model.actions.audits.AuditSingleInput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -259,7 +258,7 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
/*******************************************************************************
** add a record to the step output, e.g., for going through to the next step.
**
*******************************************************************************/
public void addRecord(QRecord record)
{
@ -272,16 +271,6 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
/***************************************************************************
** add a RecordEntity to the step output, e.g., for going through to the next step.
***************************************************************************/
public void addRecordEntity(QRecordEntity recordEntity)
{
addRecord(recordEntity.toQRecord());
}
/*******************************************************************************
** Getter for auditInputList
*******************************************************************************/
@ -385,13 +374,7 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
.map(step -> (QFrontendStepMetaData) step)
.toList());
ProcessMetaDataAdjustment processMetaDataAdjustment = getProcessMetaDataAdjustment();
if(processMetaDataAdjustment == null)
{
processMetaDataAdjustment = new ProcessMetaDataAdjustment();
}
processMetaDataAdjustment.setUpdatedFrontendStepList(updatedFrontendStepList);
setProcessMetaDataAdjustment(processMetaDataAdjustment);
setUpdatedFrontendStepList(updatedFrontendStepList);
}
@ -428,21 +411,21 @@ public class RunBackendStepOutput extends AbstractActionOutput implements Serial
/*******************************************************************************
** Getter for ProcessMetaDataAdjustment (pass-through to processState)
** Getter for updatedFrontendStepList
*******************************************************************************/
public ProcessMetaDataAdjustment getProcessMetaDataAdjustment()
public List<QFrontendStepMetaData> getUpdatedFrontendStepList()
{
return (this.processState.getProcessMetaDataAdjustment());
return (this.processState.getUpdatedFrontendStepList());
}
/*******************************************************************************
** Setter for updatedFrontendStepList (pass-through to processState)
** Setter for updatedFrontendStepList
*******************************************************************************/
public void setProcessMetaDataAdjustment(ProcessMetaDataAdjustment processMetaDataAdjustment)
public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
{
this.processState.setProcessMetaDataAdjustment(processMetaDataAdjustment);
this.processState.setUpdatedFrontendStepList(updatedFrontendStepList);
}
}

View File

@ -49,7 +49,6 @@ public class RunProcessInput extends AbstractActionInput
private ProcessState processState;
private FrontendStepBehavior frontendStepBehavior = FrontendStepBehavior.BREAK;
private String startAfterStep;
private String startAtStep;
private String processUUID;
private AsyncJobCallback asyncJobCallback;
@ -452,35 +451,4 @@ public class RunProcessInput extends AbstractActionInput
{
return asyncJobCallback;
}
/*******************************************************************************
** Getter for startAtStep
*******************************************************************************/
public String getStartAtStep()
{
return (this.startAtStep);
}
/*******************************************************************************
** Setter for startAtStep
*******************************************************************************/
public void setStartAtStep(String startAtStep)
{
this.startAtStep = startAtStep;
}
/*******************************************************************************
** Fluent setter for startAtStep
*******************************************************************************/
public RunProcessInput withStartAtStep(String startAtStep)
{
this.startAtStep = startAtStep;
return (this);
}
}

View File

@ -33,7 +33,6 @@ import java.util.Optional;
import com.kingsrook.qqq.backend.core.model.actions.AbstractActionOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendStepMetaData;
import com.kingsrook.qqq.backend.core.utils.ObjectUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
@ -337,12 +336,7 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab
*******************************************************************************/
public void setUpdatedFrontendStepList(List<QFrontendStepMetaData> updatedFrontendStepList)
{
if(this.processState.getProcessMetaDataAdjustment() == null)
{
this.processState.setProcessMetaDataAdjustment(new ProcessMetaDataAdjustment());
}
this.processState.getProcessMetaDataAdjustment().setUpdatedFrontendStepList(updatedFrontendStepList);
this.processState.setUpdatedFrontendStepList(updatedFrontendStepList);
}
@ -352,27 +346,7 @@ public class RunProcessOutput extends AbstractActionOutput implements Serializab
*******************************************************************************/
public List<QFrontendStepMetaData> getUpdatedFrontendStepList()
{
return ObjectUtils.tryElse(() -> this.processState.getProcessMetaDataAdjustment().getUpdatedFrontendStepList(), null);
}
/*******************************************************************************
** Getter for processMetaDataAdjustment
*******************************************************************************/
public ProcessMetaDataAdjustment getProcessMetaDataAdjustment()
{
return (this.processState.getProcessMetaDataAdjustment());
}
/*******************************************************************************
** Setter for processMetaDataAdjustment
*******************************************************************************/
public void setProcessMetaDataAdjustment(ProcessMetaDataAdjustment processMetaDataAdjustment)
{
this.processState.setProcessMetaDataAdjustment(processMetaDataAdjustment);
return this.processState.getUpdatedFrontendStepList();
}
}

View File

@ -1,31 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
/*******************************************************************************
**
*******************************************************************************/
public enum CriteriaOption implements CriteriaOptionInterface
{
CASE_INSENSITIVE;
}

View File

@ -1,30 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.actions.tables.query;
/*******************************************************************************
**
*******************************************************************************/
public interface CriteriaOptionInterface
{
}

View File

@ -82,7 +82,7 @@ public class JoinsContext
/////////////////////////////////////////////////////////////////////////////
// we will get a TON of more output if this gets turned up, so be cautious //
/////////////////////////////////////////////////////////////////////////////
private Level logLevel = Level.OFF;
private Level logLevel = Level.OFF;
private Level logLevelForFilter = Level.OFF;
@ -404,12 +404,6 @@ public class JoinsContext
chainIsInner = false;
}
if(hasAllAccessKey(recordSecurityLock))
{
queryJoin.withType(QueryJoin.Type.LEFT);
chainIsInner = false;
}
addQueryJoin(queryJoin, "forRecordSecurityLock (non-flipped)", "- ");
addedQueryJoins.add(queryJoin);
tmpTable = instance.getTable(join.getRightTable());
@ -429,12 +423,6 @@ public class JoinsContext
chainIsInner = false;
}
if(hasAllAccessKey(recordSecurityLock))
{
queryJoin.withType(QueryJoin.Type.LEFT);
chainIsInner = false;
}
addQueryJoin(queryJoin, "forRecordSecurityLock (flipped)", "- ");
addedQueryJoins.add(queryJoin);
tmpTable = instance.getTable(join.getLeftTable());
@ -468,53 +456,44 @@ public class JoinsContext
/***************************************************************************
**
***************************************************************************/
private boolean hasAllAccessKey(RecordSecurityLock recordSecurityLock)
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// check if the key type has an all-access key, and if so, if it's set to true for the current user/session //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
QSecurityKeyType securityKeyType = instance.getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
{
QSession session = QContext.getQSession();
if(session.hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
{
return (true);
}
}
return (false);
}
/*******************************************************************************
**
*******************************************************************************/
private void addSubFilterForRecordSecurityLock(RecordSecurityLock recordSecurityLock, QTableMetaData table, String tableNameOrAlias, boolean isOuter, QueryJoin sourceQueryJoin)
{
boolean haveAllAccessKey = hasAllAccessKey(recordSecurityLock);
if(haveAllAccessKey)
{
if(sourceQueryJoin != null)
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// in case the queryJoin object is re-used between queries, and its security criteria need to be different (!!), reset it //
// this can be exposed in tests - maybe not entirely expected in real-world, but seems safe enough //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sourceQueryJoin.withSecurityCriteria(new ArrayList<>());
}
QSession session = QContext.getQSession();
////////////////////////////////////////////////////////////////////////////////////////
// if we're in an AND filter, then we don't need a criteria for this lock, so return. //
////////////////////////////////////////////////////////////////////////////////////////
boolean inAnAndFilter = securityFilterCursor.getBooleanOperator() == QQueryFilter.BooleanOperator.AND;
if(inAnAndFilter)
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
// check if the key type has an all-access key, and if so, if it's set to true for the current user/session //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
QSecurityKeyType securityKeyType = instance.getSecurityKeyType(recordSecurityLock.getSecurityKeyType());
boolean haveAllAccessKey = false;
if(StringUtils.hasContent(securityKeyType.getAllAccessKeyName()))
{
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if we have all-access on this key, then we don't need a criterion for it (as long as we're in an AND filter) //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(session.hasSecurityKeyValue(securityKeyType.getAllAccessKeyName(), true, QFieldType.BOOLEAN))
{
return;
haveAllAccessKey = true;
if(sourceQueryJoin != null)
{
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// in case the queryJoin object is re-used between queries, and its security criteria need to be different (!!), reset it //
// this can be exposed in tests - maybe not entirely expected in real-world, but seems safe enough //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
sourceQueryJoin.withSecurityCriteria(new ArrayList<>());
}
////////////////////////////////////////////////////////////////////////////////////////
// if we're in an AND filter, then we don't need a criteria for this lock, so return. //
////////////////////////////////////////////////////////////////////////////////////////
boolean inAnAndFilter = securityFilterCursor.getBooleanOperator() == QQueryFilter.BooleanOperator.AND;
if(inAnAndFilter)
{
return;
}
}
}
@ -566,7 +545,7 @@ public class JoinsContext
}
else
{
List<Serializable> securityKeyValues = QContext.getQSession().getSecurityKeyValues(recordSecurityLock.getSecurityKeyType(), type);
List<Serializable> securityKeyValues = session.getSecurityKeyValues(recordSecurityLock.getSecurityKeyType(), type);
if(CollectionUtils.nullSafeIsEmpty(securityKeyValues))
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -26,10 +26,8 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
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;
@ -46,7 +44,7 @@ public class QFilterCriteria implements Serializable, Cloneable
{
private static final QLogger LOG = QLogger.getLogger(QFilterCriteria.class);
private String fieldName;
private String fieldName;
private QCriteriaOperator operator;
private List<Serializable> values;
@ -55,8 +53,6 @@ public class QFilterCriteria implements Serializable, Cloneable
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private String otherFieldName;
private Set<CriteriaOptionInterface> options = null;
/*******************************************************************************
@ -73,13 +69,6 @@ public class QFilterCriteria implements Serializable, Cloneable
clone.values = new ArrayList<>();
clone.values.addAll(values);
}
if(options != null)
{
clone.options = new HashSet<>();
clone.options.addAll(options);
}
return clone;
}
catch(CloneNotSupportedException e)
@ -396,78 +385,4 @@ public class QFilterCriteria implements Serializable, Cloneable
return Objects.hash(fieldName, operator, values, otherFieldName);
}
/*******************************************************************************
** Getter for options
*******************************************************************************/
public Set<CriteriaOptionInterface> getOptions()
{
return (this.options);
}
/*******************************************************************************
** Setter for options
*******************************************************************************/
public void setOptions(Set<CriteriaOptionInterface> options)
{
this.options = options;
}
/*******************************************************************************
** Fluent setter for options
*******************************************************************************/
public QFilterCriteria withOptions(Set<CriteriaOptionInterface> options)
{
this.options = options;
return (this);
}
/***************************************************************************
**
***************************************************************************/
public QFilterCriteria withOption(CriteriaOptionInterface option)
{
if(options == null)
{
options = new HashSet<>();
}
options.add(option);
return (this);
}
/***************************************************************************
**
***************************************************************************/
public QFilterCriteria withoutOption(CriteriaOptionInterface option)
{
if(options != null)
{
options.remove(option);
}
return (this);
}
/***************************************************************************
**
***************************************************************************/
public boolean hasOption(CriteriaOptionInterface option)
{
if(options == null)
{
return (false);
}
return (options.contains(option));
}
}

View File

@ -55,16 +55,6 @@ public class QQueryFilter implements Serializable, Cloneable
private BooleanOperator booleanOperator = BooleanOperator.AND;
private List<QQueryFilter> subFilters = new ArrayList<>();
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// initial intent here was - put, e.g., UNION between multiple SELECT (with the individual selects being defined in subFilters) //
// but, actually SQL would let us do, e.g., SELECT UNION SELECT INTERSECT SELECT //
// so - we could see a future implementation where we: //
// - used the top-level subFilterSetOperator to indicate hat we are doing a multi-query set-operation query. //
// - looked within the subFilter, to see if it specified a subFilterSetOperator - and use that operator before that query //
// but - in v0, just using the one at the top-level works //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private SubFilterSetOperator subFilterSetOperator = null;
////////////////////////////////////////////////////////////////////////////////////////////////////////////
// skip & limit are meant to only apply to QueryAction (at least at the initial time they are added here) //
// e.g., they are ignored in CountAction, AggregateAction, etc, where their meanings may be less obvious //
@ -85,19 +75,6 @@ public class QQueryFilter implements Serializable, Cloneable
/*******************************************************************************
**
*******************************************************************************/
public enum SubFilterSetOperator
{
UNION,
UNION_ALL,
INTERSECT,
EXCEPT
}
/*******************************************************************************
** Constructor
**
@ -822,51 +799,4 @@ public class QQueryFilter implements Serializable, Cloneable
}
}
/*******************************************************************************
** Getter for subFilterSetOperator
*******************************************************************************/
public SubFilterSetOperator getSubFilterSetOperator()
{
return (this.subFilterSetOperator);
}
/*******************************************************************************
** Setter for subFilterSetOperator
*******************************************************************************/
public void setSubFilterSetOperator(SubFilterSetOperator subFilterSetOperator)
{
this.subFilterSetOperator = subFilterSetOperator;
}
/*******************************************************************************
** Fluent setter for subFilterSetOperator
*******************************************************************************/
public QQueryFilter withSubFilterSetOperator(SubFilterSetOperator subFilterSetOperator)
{
this.subFilterSetOperator = subFilterSetOperator;
return (this);
}
/***************************************************************************
**
***************************************************************************/
public void applyCriteriaOptionToAllCriteria(CriteriaOptionInterface criteriaOption)
{
for(QFilterCriteria criteria : CollectionUtils.nonNullList(this.criteria))
{
criteria.withOption(criteriaOption);
}
for(QQueryFilter subFilter : CollectionUtils.nonNullList(subFilters))
{
subFilter.applyCriteriaOptionToAllCriteria(criteriaOption);
}
}
}

View File

@ -22,17 +22,15 @@
package com.kingsrook.qqq.backend.core.model.actions.tables.storage;
import java.io.Serializable;
import com.kingsrook.qqq.backend.core.model.actions.AbstractTableActionInput;
/*******************************************************************************
** Input for Storage actions.
*******************************************************************************/
public class StorageInput extends AbstractTableActionInput implements Serializable
public class StorageInput extends AbstractTableActionInput
{
private String reference;
private String contentType;
@ -76,35 +74,4 @@ public class StorageInput extends AbstractTableActionInput implements Serializab
return (this);
}
/*******************************************************************************
** Getter for contentType
*******************************************************************************/
public String getContentType()
{
return (this.contentType);
}
/*******************************************************************************
** Setter for contentType
*******************************************************************************/
public void setContentType(String contentType)
{
this.contentType = contentType;
}
/*******************************************************************************
** Fluent setter for contentType
*******************************************************************************/
public StorageInput withContentType(String contentType)
{
this.contentType = contentType;
return (this);
}
}

View File

@ -38,10 +38,9 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput implemen
private QQueryFilter defaultQueryFilter;
private String searchTerm;
private List<Serializable> idList;
private List<String> labelList;
private Integer skip = 0;
private Integer limit = 250;
private Integer limit = 100;
@ -282,35 +281,4 @@ public class SearchPossibleValueSourceInput extends AbstractActionInput implemen
this.limit = limit;
return (this);
}
/*******************************************************************************
** Getter for labelList
*******************************************************************************/
public List<String> getLabelList()
{
return (this.labelList);
}
/*******************************************************************************
** Setter for labelList
*******************************************************************************/
public void setLabelList(List<String> labelList)
{
this.labelList = labelList;
}
/*******************************************************************************
** Fluent setter for labelList
*******************************************************************************/
public SearchPossibleValueSourceInput withLabelList(List<String> labelList)
{
this.labelList = labelList;
return (this);
}
}

View File

@ -35,7 +35,6 @@ public class SearchPossibleValueSourceOutput extends AbstractActionOutput
{
private List<QPossibleValue<?>> results = new ArrayList<>();
private String warning;
/*******************************************************************************
@ -89,35 +88,4 @@ public class SearchPossibleValueSourceOutput extends AbstractActionOutput
return (this);
}
/*******************************************************************************
** Getter for warning
*******************************************************************************/
public String getWarning()
{
return (this.warning);
}
/*******************************************************************************
** Setter for warning
*******************************************************************************/
public void setWarning(String warning)
{
this.warning = warning;
}
/*******************************************************************************
** Fluent setter for warning
*******************************************************************************/
public SearchPossibleValueSourceOutput withWarning(String warning)
{
this.warning = warning;
return (this);
}
}

View File

@ -22,9 +22,6 @@
package com.kingsrook.qqq.backend.core.model.dashboard.widgets;
import java.util.List;
/*******************************************************************************
** Model containing datastructure expected by frontend alert widget
**
@ -43,10 +40,8 @@ public class AlertData extends QWidgetData
private String html;
private AlertType alertType;
private Boolean hideWidget = false;
private List<String> bulletList;
private String html;
private AlertType alertType;
@ -144,66 +139,4 @@ public class AlertData extends QWidgetData
return (this);
}
/*******************************************************************************
** Getter for hideWidget
*******************************************************************************/
public boolean getHideWidget()
{
return (this.hideWidget);
}
/*******************************************************************************
** Setter for hideWidget
*******************************************************************************/
public void setHideWidget(boolean hideWidget)
{
this.hideWidget = hideWidget;
}
/*******************************************************************************
** Fluent setter for hideWidget
*******************************************************************************/
public AlertData withHideWidget(boolean hideWidget)
{
this.hideWidget = hideWidget;
return (this);
}
/*******************************************************************************
** Getter for bulletList
*******************************************************************************/
public List<String> getBulletList()
{
return (this.bulletList);
}
/*******************************************************************************
** Setter for bulletList
*******************************************************************************/
public void setBulletList(List<String> bulletList)
{
this.bulletList = bulletList;
}
/*******************************************************************************
** Fluent setter for bulletList
*******************************************************************************/
public AlertData withBulletList(List<String> bulletList)
{
this.bulletList = bulletList;
return (this);
}
}

View File

@ -39,19 +39,13 @@ public class ChildRecordListData extends QWidgetData
private QueryOutput queryOutput;
private QTableMetaData childTableMetaData;
private String tableName;
private String tablePath;
private String viewAllLink;
private Integer totalRows;
private Boolean disableRowClick = false;
private Boolean allowRecordEdit = false;
private Boolean allowRecordDelete = false;
private Boolean isInProcess = false;
private boolean canAddChildRecord = false;
private Map<String, Serializable> defaultValuesForNewChildRecords;
private Set<String> disabledFieldsForNewChildRecords;
private Map<String, String> defaultValuesForNewChildRecordsFromParentFields;
@ -358,204 +352,4 @@ public class ChildRecordListData extends QWidgetData
return (this);
}
/*******************************************************************************
** Getter for tableName
*******************************************************************************/
public String getTableName()
{
return (this.tableName);
}
/*******************************************************************************
** Setter for tableName
*******************************************************************************/
public void setTableName(String tableName)
{
this.tableName = tableName;
}
/*******************************************************************************
** Fluent setter for tableName
*******************************************************************************/
public ChildRecordListData withTableName(String tableName)
{
this.tableName = tableName;
return (this);
}
/*******************************************************************************
** Fluent setter for tablePath
*******************************************************************************/
public ChildRecordListData withTablePath(String tablePath)
{
this.tablePath = tablePath;
return (this);
}
/*******************************************************************************
** Getter for disableRowClick
*******************************************************************************/
public Boolean getDisableRowClick()
{
return (this.disableRowClick);
}
/*******************************************************************************
** Setter for disableRowClick
*******************************************************************************/
public void setDisableRowClick(Boolean disableRowClick)
{
this.disableRowClick = disableRowClick;
}
/*******************************************************************************
** Fluent setter for disableRowClick
*******************************************************************************/
public ChildRecordListData withDisableRowClick(Boolean disableRowClick)
{
this.disableRowClick = disableRowClick;
return (this);
}
/*******************************************************************************
** Getter for allowRecordEdit
*******************************************************************************/
public Boolean getAllowRecordEdit()
{
return (this.allowRecordEdit);
}
/*******************************************************************************
** Setter for allowRecordEdit
*******************************************************************************/
public void setAllowRecordEdit(Boolean allowRecordEdit)
{
this.allowRecordEdit = allowRecordEdit;
}
/*******************************************************************************
** Fluent setter for allowRecordEdit
*******************************************************************************/
public ChildRecordListData withAllowRecordEdit(Boolean allowRecordEdit)
{
this.allowRecordEdit = allowRecordEdit;
return (this);
}
/*******************************************************************************
** Getter for allowRecordDelete
*******************************************************************************/
public Boolean getAllowRecordDelete()
{
return (this.allowRecordDelete);
}
/*******************************************************************************
** Setter for allowRecordDelete
*******************************************************************************/
public void setAllowRecordDelete(Boolean allowRecordDelete)
{
this.allowRecordDelete = allowRecordDelete;
}
/*******************************************************************************
** Fluent setter for allowRecordDelete
*******************************************************************************/
public ChildRecordListData withAllowRecordDelete(Boolean allowRecordDelete)
{
this.allowRecordDelete = allowRecordDelete;
return (this);
}
/*******************************************************************************
** Getter for isInProcess
*******************************************************************************/
public Boolean getIsInProcess()
{
return (this.isInProcess);
}
/*******************************************************************************
** Setter for isInProcess
*******************************************************************************/
public void setIsInProcess(Boolean isInProcess)
{
this.isInProcess = isInProcess;
}
/*******************************************************************************
** Fluent setter for isInProcess
*******************************************************************************/
public ChildRecordListData withIsInProcess(Boolean isInProcess)
{
this.isInProcess = isInProcess;
return (this);
}
/*******************************************************************************
** Getter for defaultValuesForNewChildRecordsFromParentFields
*******************************************************************************/
public Map<String, String> getDefaultValuesForNewChildRecordsFromParentFields()
{
return (this.defaultValuesForNewChildRecordsFromParentFields);
}
/*******************************************************************************
** Setter for defaultValuesForNewChildRecordsFromParentFields
*******************************************************************************/
public void setDefaultValuesForNewChildRecordsFromParentFields(Map<String, String> defaultValuesForNewChildRecordsFromParentFields)
{
this.defaultValuesForNewChildRecordsFromParentFields = defaultValuesForNewChildRecordsFromParentFields;
}
/*******************************************************************************
** Fluent setter for defaultValuesForNewChildRecordsFromParentFields
*******************************************************************************/
public ChildRecordListData withDefaultValuesForNewChildRecordsFromParentFields(Map<String, String> defaultValuesForNewChildRecordsFromParentFields)
{
this.defaultValuesForNewChildRecordsFromParentFields = defaultValuesForNewChildRecordsFromParentFields;
return (this);
}
}

View File

@ -40,20 +40,6 @@ public class CompositeWidgetData extends AbstractBlockWidgetData<CompositeWidget
{
private List<AbstractBlockWidgetData<?, ?, ?, ?>> blocks = new ArrayList<>();
private ModalMode modalMode;
/***************************************************************************
**
***************************************************************************/
public enum ModalMode
{
MODAL
}
private Layout layout;
private Map<String, Serializable> styleOverrides = new HashMap<>();
private String overlayHtml;
@ -66,14 +52,12 @@ public class CompositeWidgetData extends AbstractBlockWidgetData<CompositeWidget
*******************************************************************************/
public enum Layout
{
/////////////////////////////////////////////////////////
// note, these are used in QQQ FMD CompositeWidget.tsx //
// and qqq-android CompositeWidgetBlock.kt //
/////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////
// note, these are used in QQQ FMD CompositeWidgetData.tsx //
/////////////////////////////////////////////////////////////
FLEX_COLUMN,
FLEX_ROW_WRAPPED,
FLEX_ROW_SPACE_BETWEEN,
FLEX_ROW_CENTER,
TABLE_SUB_ROW_DETAILS,
BADGES_WRAPPER
}
@ -322,35 +306,4 @@ public class CompositeWidgetData extends AbstractBlockWidgetData<CompositeWidget
this.overlayStyleOverrides.put(key, value);
}
/*******************************************************************************
** Getter for modalMode
*******************************************************************************/
public ModalMode getModalMode()
{
return (this.modalMode);
}
/*******************************************************************************
** Setter for modalMode
*******************************************************************************/
public void setModalMode(ModalMode modalMode)
{
this.modalMode = modalMode;
}
/*******************************************************************************
** Fluent setter for modalMode
*******************************************************************************/
public CompositeWidgetData withModalMode(ModalMode modalMode)
{
this.modalMode = modalMode;
return (this);
}
}

View File

@ -34,12 +34,8 @@ public class FilterAndColumnsSetupData extends QWidgetData
private String tableName;
private Boolean allowVariables = false;
private Boolean hideColumns = false;
private Boolean hidePreview = false;
private List<String> filterDefaultFieldNames;
private String filterFieldName = "queryFilterJson";
private String columnFieldName = "columnsJson";
/*******************************************************************************
@ -197,97 +193,4 @@ public class FilterAndColumnsSetupData extends QWidgetData
return (this);
}
/*******************************************************************************
** Getter for hidePreview
*******************************************************************************/
public Boolean getHidePreview()
{
return (this.hidePreview);
}
/*******************************************************************************
** Setter for hidePreview
*******************************************************************************/
public void setHidePreview(Boolean hidePreview)
{
this.hidePreview = hidePreview;
}
/*******************************************************************************
** Fluent setter for hidePreview
*******************************************************************************/
public FilterAndColumnsSetupData withHidePreview(Boolean hidePreview)
{
this.hidePreview = hidePreview;
return (this);
}
/*******************************************************************************
** Getter for filterFieldName
*******************************************************************************/
public String getFilterFieldName()
{
return (this.filterFieldName);
}
/*******************************************************************************
** Setter for filterFieldName
*******************************************************************************/
public void setFilterFieldName(String filterFieldName)
{
this.filterFieldName = filterFieldName;
}
/*******************************************************************************
** Fluent setter for filterFieldName
*******************************************************************************/
public FilterAndColumnsSetupData withFilterFieldName(String filterFieldName)
{
this.filterFieldName = filterFieldName;
return (this);
}
/*******************************************************************************
** Getter for columnFieldName
*******************************************************************************/
public String getColumnFieldName()
{
return (this.columnFieldName);
}
/*******************************************************************************
** Setter for columnFieldName
*******************************************************************************/
public void setColumnFieldName(String columnFieldName)
{
this.columnFieldName = columnFieldName;
}
/*******************************************************************************
** Fluent setter for columnFieldName
*******************************************************************************/
public FilterAndColumnsSetupData withColumnFieldName(String columnFieldName)
{
this.columnFieldName = columnFieldName;
return (this);
}
}

View File

@ -31,7 +31,7 @@ import java.util.Map;
** Base class for the data returned by rendering a Widget.
**
*******************************************************************************/
public abstract class QWidgetData implements Serializable
public abstract class QWidgetData
{
private String label;
private String sublabel;

View File

@ -52,11 +52,6 @@ public abstract class AbstractBlockWidgetData<
private V values;
private SX styles;
///////////////////////////////////////////////////////////////////////////////////
// optional field name to act as a 'guard' for the block - e.g., only include it //
// if the value for this field is true //
///////////////////////////////////////////////////////////////////////////////////
private String conditional;
/*******************************************************************************
@ -448,35 +443,4 @@ public abstract class AbstractBlockWidgetData<
return (T) this;
}
/*******************************************************************************
** Getter for conditional
*******************************************************************************/
public String getConditional()
{
return (this.conditional);
}
/*******************************************************************************
** Setter for conditional
*******************************************************************************/
public void setConditional(String conditional)
{
this.conditional = conditional;
}
/*******************************************************************************
** Fluent setter for conditional
*******************************************************************************/
public AbstractBlockWidgetData withConditional(String conditional)
{
this.conditional = conditional;
return (this);
}
}

View File

@ -1,45 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.audio;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseSlots;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseStyles;
/*******************************************************************************
** block that plays an audio file
*******************************************************************************/
public class AudioBlockData extends AbstractBlockWidgetData<AudioBlockData, AudioValues, BaseSlots, BaseStyles>
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getBlockTypeName()
{
return "AUDIO";
}
}

View File

@ -1,130 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.audio;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
/*******************************************************************************
**
*******************************************************************************/
public class AudioValues implements BlockValuesInterface
{
private String path;
private boolean showControls = false;
private boolean autoPlay = true;
/*******************************************************************************
** Getter for path
*******************************************************************************/
public String getPath()
{
return (this.path);
}
/*******************************************************************************
** Setter for path
*******************************************************************************/
public void setPath(String path)
{
this.path = path;
}
/*******************************************************************************
** Fluent setter for path
*******************************************************************************/
public AudioValues withPath(String path)
{
this.path = path;
return (this);
}
/*******************************************************************************
** Getter for showControls
*******************************************************************************/
public boolean getShowControls()
{
return (this.showControls);
}
/*******************************************************************************
** Setter for showControls
*******************************************************************************/
public void setShowControls(boolean showControls)
{
this.showControls = showControls;
}
/*******************************************************************************
** Fluent setter for showControls
*******************************************************************************/
public AudioValues withShowControls(boolean showControls)
{
this.showControls = showControls;
return (this);
}
/*******************************************************************************
** Getter for autoPlay
*******************************************************************************/
public boolean getAutoPlay()
{
return (this.autoPlay);
}
/*******************************************************************************
** Setter for autoPlay
*******************************************************************************/
public void setAutoPlay(boolean autoPlay)
{
this.autoPlay = autoPlay;
}
/*******************************************************************************
** Fluent setter for autoPlay
*******************************************************************************/
public AudioValues withAutoPlay(boolean autoPlay)
{
this.autoPlay = autoPlay;
return (this);
}
}

View File

@ -30,335 +30,4 @@ import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStyles
*******************************************************************************/
public class BaseStyles implements BlockStylesInterface
{
private Directional<String> padding;
private String backgroundColor;
/***************************************************************************
**
***************************************************************************/
public static class Directional<T>
{
private T top;
private T bottom;
private T left;
private T right;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public Directional()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public Directional(T top, T right, T bottom, T left)
{
this.top = top;
this.right = right;
this.bottom = bottom;
this.left = left;
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> of(T top, T right, T bottom, T left)
{
return (new Directional<>(top, right, bottom, left));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> of(T value)
{
return (new Directional<>(value, value, value, value));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofTop(T top)
{
return (new Directional<>(top, null, null, null));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofRight(T right)
{
return (new Directional<>(null, right, null, null));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofBottom(T bottom)
{
return (new Directional<>(null, null, bottom, null));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofLeft(T left)
{
return (new Directional<>(null, null, null, left));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofX(T x)
{
return (new Directional<>(null, x, null, x));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofY(T y)
{
return (new Directional<>(y, null, y, null));
}
/***************************************************************************
**
***************************************************************************/
public static <T> Directional<T> ofXY(T x, T y)
{
return (new Directional<>(y, x, y, x));
}
/*******************************************************************************
** Getter for top
**
*******************************************************************************/
public T getTop()
{
return top;
}
/*******************************************************************************
** Setter for top
**
*******************************************************************************/
public void setTop(T top)
{
this.top = top;
}
/*******************************************************************************
** Fluent setter for top
**
*******************************************************************************/
public Directional<T> withTop(T top)
{
this.top = top;
return (this);
}
/*******************************************************************************
** Getter for bottom
**
*******************************************************************************/
public T getBottom()
{
return bottom;
}
/*******************************************************************************
** Setter for bottom
**
*******************************************************************************/
public void setBottom(T bottom)
{
this.bottom = bottom;
}
/*******************************************************************************
** Fluent setter for bottom
**
*******************************************************************************/
public Directional<T> withBottom(T bottom)
{
this.bottom = bottom;
return (this);
}
/*******************************************************************************
** Getter for left
**
*******************************************************************************/
public T getLeft()
{
return left;
}
/*******************************************************************************
** Setter for left
**
*******************************************************************************/
public void setLeft(T left)
{
this.left = left;
}
/*******************************************************************************
** Fluent setter for left
**
*******************************************************************************/
public Directional<T> withLeft(T left)
{
this.left = left;
return (this);
}
/*******************************************************************************
** Getter for right
**
*******************************************************************************/
public T getRight()
{
return right;
}
/*******************************************************************************
** Setter for right
**
*******************************************************************************/
public void setRight(T right)
{
this.right = right;
}
/*******************************************************************************
** Fluent setter for right
**
*******************************************************************************/
public Directional<T> withRight(T right)
{
this.right = right;
return (this);
}
}
/*******************************************************************************
** Getter for padding
*******************************************************************************/
public Directional<String> getPadding()
{
return (this.padding);
}
/*******************************************************************************
** Setter for padding
*******************************************************************************/
public void setPadding(Directional<String> padding)
{
this.padding = padding;
}
/*******************************************************************************
** Fluent setter for padding
*******************************************************************************/
public BaseStyles withPadding(Directional<String> padding)
{
this.padding = padding;
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 BaseStyles withBackgroundColor(String backgroundColor)
{
this.backgroundColor = backgroundColor;
return (this);
}
}

View File

@ -1,46 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.button;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseSlots;
/*******************************************************************************
** a button (for a process - not sure yet what this could do in a standalone
** widget?) to submit the process screen to run a specific action (e.g., not just
** 'next'), or do other control-ish things
*******************************************************************************/
public class ButtonBlockData extends AbstractBlockWidgetData<ButtonBlockData, ButtonValues, BaseSlots, ButtonStyles>
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getBlockTypeName()
{
return "BUTTON";
}
}

View File

@ -1,143 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.button;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStylesInterface;
/*******************************************************************************
**
*******************************************************************************/
public class ButtonStyles implements BlockStylesInterface
{
private String color;
private String format;
/***************************************************************************
**
***************************************************************************/
public enum StandardColor
{
SUCCESS,
WARNING,
ERROR,
INFO,
MUTED
}
/***************************************************************************
**
***************************************************************************/
public enum StandardFormat
{
OUTLINED,
FILLED,
TEXT
}
/*******************************************************************************
** Getter for color
*******************************************************************************/
public String getColor()
{
return (this.color);
}
/*******************************************************************************
** Setter for color
*******************************************************************************/
public void setColor(String color)
{
this.color = color;
}
/*******************************************************************************
** Fluent setter for color
*******************************************************************************/
public ButtonStyles withColor(String color)
{
this.color = color;
return (this);
}
/*******************************************************************************
** Getter for format
*******************************************************************************/
public String getFormat()
{
return (this.format);
}
/*******************************************************************************
** Setter for format
*******************************************************************************/
public void setFormat(String format)
{
this.format = format;
}
/*******************************************************************************
** Fluent setter for format
*******************************************************************************/
public ButtonStyles withFormat(String format)
{
this.format = format;
return (this);
}
/*******************************************************************************
** Setter for format
*******************************************************************************/
public void setFormat(StandardFormat format)
{
this.format = (format == null ? null : format.name().toLowerCase());
}
/*******************************************************************************
** Fluent setter for format
*******************************************************************************/
public ButtonStyles withFormat(StandardFormat format)
{
setFormat(format);
return (this);
}
}

View File

@ -1,218 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.button;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
/*******************************************************************************
**
*******************************************************************************/
public class ButtonValues implements BlockValuesInterface
{
private String label;
private String actionCode;
private String controlCode;
private QIcon startIcon;
private QIcon endIcon;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ButtonValues()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ButtonValues(String label, String actionCode)
{
setLabel(label);
setActionCode(actionCode);
}
/*******************************************************************************
** Getter for label
*******************************************************************************/
public String getLabel()
{
return (this.label);
}
/*******************************************************************************
** Setter for label
*******************************************************************************/
public void setLabel(String label)
{
this.label = label;
}
/*******************************************************************************
** Fluent setter for label
*******************************************************************************/
public ButtonValues withLabel(String label)
{
this.label = label;
return (this);
}
/*******************************************************************************
** Getter for actionCode
*******************************************************************************/
public String getActionCode()
{
return (this.actionCode);
}
/*******************************************************************************
** Setter for actionCode
*******************************************************************************/
public void setActionCode(String actionCode)
{
this.actionCode = actionCode;
}
/*******************************************************************************
** Fluent setter for actionCode
*******************************************************************************/
public ButtonValues withActionCode(String actionCode)
{
this.actionCode = actionCode;
return (this);
}
/*******************************************************************************
** Getter for startIcon
*******************************************************************************/
public QIcon getStartIcon()
{
return (this.startIcon);
}
/*******************************************************************************
** Setter for startIcon
*******************************************************************************/
public void setStartIcon(QIcon startIcon)
{
this.startIcon = startIcon;
}
/*******************************************************************************
** Fluent setter for startIcon
*******************************************************************************/
public ButtonValues withStartIcon(QIcon startIcon)
{
this.startIcon = startIcon;
return (this);
}
/*******************************************************************************
** Getter for endIcon
*******************************************************************************/
public QIcon getEndIcon()
{
return (this.endIcon);
}
/*******************************************************************************
** Setter for endIcon
*******************************************************************************/
public void setEndIcon(QIcon endIcon)
{
this.endIcon = endIcon;
}
/*******************************************************************************
** Fluent setter for endIcon
*******************************************************************************/
public ButtonValues withEndIcon(QIcon endIcon)
{
this.endIcon = endIcon;
return (this);
}
/*******************************************************************************
** Getter for controlCode
*******************************************************************************/
public String getControlCode()
{
return (this.controlCode);
}
/*******************************************************************************
** Setter for controlCode
*******************************************************************************/
public void setControlCode(String controlCode)
{
this.controlCode = controlCode;
}
/*******************************************************************************
** Fluent setter for controlCode
*******************************************************************************/
public ButtonValues withControlCode(String controlCode)
{
this.controlCode = controlCode;
return (this);
}
}

View File

@ -1,44 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.image;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseSlots;
/*******************************************************************************
** block to display an image
*******************************************************************************/
public class ImageBlockData extends AbstractBlockWidgetData<ImageBlockData, ImageValues, BaseSlots, ImageStyles>
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getBlockTypeName()
{
return "IMAGE";
}
}

View File

@ -1,108 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.image;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseStyles;
/*******************************************************************************
**
*******************************************************************************/
public class ImageStyles extends BaseStyles
{
private String width;
private String height;
/*******************************************************************************
** Fluent setter for padding
*******************************************************************************/
@Override
public ImageStyles withPadding(Directional<String> padding)
{
super.setPadding(padding);
return (this);
}
/*******************************************************************************
** Getter for width
*******************************************************************************/
public String getWidth()
{
return (this.width);
}
/*******************************************************************************
** Setter for width
*******************************************************************************/
public void setWidth(String width)
{
this.width = width;
}
/*******************************************************************************
** Fluent setter for width
*******************************************************************************/
public ImageStyles withWidth(String width)
{
this.width = width;
return (this);
}
/*******************************************************************************
** Getter for height
*******************************************************************************/
public String getHeight()
{
return (this.height);
}
/*******************************************************************************
** Setter for height
*******************************************************************************/
public void setHeight(String height)
{
this.height = height;
}
/*******************************************************************************
** Fluent setter for height
*******************************************************************************/
public ImageStyles withHeight(String height)
{
this.height = height;
return (this);
}
}

View File

@ -1,98 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.image;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
/*******************************************************************************
**
*******************************************************************************/
public class ImageValues implements BlockValuesInterface
{
private String path;
private String alt;
/*******************************************************************************
** Getter for path
*******************************************************************************/
public String getPath()
{
return (this.path);
}
/*******************************************************************************
** Setter for path
*******************************************************************************/
public void setPath(String path)
{
this.path = path;
}
/*******************************************************************************
** Fluent setter for path
*******************************************************************************/
public ImageValues withPath(String path)
{
this.path = path;
return (this);
}
/*******************************************************************************
** Getter for alt
*******************************************************************************/
public String getAlt()
{
return (this.alt);
}
/*******************************************************************************
** Setter for alt
*******************************************************************************/
public void setAlt(String alt)
{
this.alt = alt;
}
/*******************************************************************************
** Fluent setter for alt
*******************************************************************************/
public ImageValues withAlt(String alt)
{
this.alt = alt;
return (this);
}
}

View File

@ -1,45 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.inputfield;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.AbstractBlockWidgetData;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseSlots;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.base.BaseStyles;
/*******************************************************************************
** block to display an input field - initially targeted at widgets-in-processes
*******************************************************************************/
public class InputFieldBlockData extends AbstractBlockWidgetData<InputFieldBlockData, InputFieldValues, BaseSlots, BaseStyles>
{
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getBlockTypeName()
{
return "INPUT_FIELD";
}
}

View File

@ -1,217 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.inputfield;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
/*******************************************************************************
**
*******************************************************************************/
public class InputFieldValues implements BlockValuesInterface
{
private QFieldMetaData fieldMetaData;
private Boolean autoFocus;
private Boolean submitOnEnter;
private Boolean hideSoftKeyboard;
private String placeholder;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public InputFieldValues()
{
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public InputFieldValues(QFieldMetaData fieldMetaData)
{
setFieldMetaData(fieldMetaData);
}
/*******************************************************************************
** Getter for fieldMetaData
*******************************************************************************/
public QFieldMetaData getFieldMetaData()
{
return (this.fieldMetaData);
}
/*******************************************************************************
** Setter for fieldMetaData
*******************************************************************************/
public void setFieldMetaData(QFieldMetaData fieldMetaData)
{
this.fieldMetaData = fieldMetaData;
}
/*******************************************************************************
** Fluent setter for fieldMetaData
*******************************************************************************/
public InputFieldValues withFieldMetaData(QFieldMetaData fieldMetaData)
{
this.fieldMetaData = fieldMetaData;
return (this);
}
/*******************************************************************************
** Getter for autoFocus
*******************************************************************************/
public Boolean getAutoFocus()
{
return (this.autoFocus);
}
/*******************************************************************************
** Setter for autoFocus
*******************************************************************************/
public void setAutoFocus(Boolean autoFocus)
{
this.autoFocus = autoFocus;
}
/*******************************************************************************
** Fluent setter for autoFocus
*******************************************************************************/
public InputFieldValues withAutoFocus(Boolean autoFocus)
{
this.autoFocus = autoFocus;
return (this);
}
/*******************************************************************************
** Getter for submitOnEnter
*******************************************************************************/
public Boolean getSubmitOnEnter()
{
return (this.submitOnEnter);
}
/*******************************************************************************
** Setter for submitOnEnter
*******************************************************************************/
public void setSubmitOnEnter(Boolean submitOnEnter)
{
this.submitOnEnter = submitOnEnter;
}
/*******************************************************************************
** Fluent setter for submitOnEnter
*******************************************************************************/
public InputFieldValues withSubmitOnEnter(Boolean submitOnEnter)
{
this.submitOnEnter = submitOnEnter;
return (this);
}
/*******************************************************************************
** Getter for placeholder
*******************************************************************************/
public String getPlaceholder()
{
return (this.placeholder);
}
/*******************************************************************************
** Setter for placeholder
*******************************************************************************/
public void setPlaceholder(String placeholder)
{
this.placeholder = placeholder;
}
/*******************************************************************************
** Fluent setter for placeholder
*******************************************************************************/
public InputFieldValues withPlaceholder(String placeholder)
{
this.placeholder = placeholder;
return (this);
}
/*******************************************************************************
** Getter for hideSoftKeyboard
*******************************************************************************/
public Boolean getHideSoftKeyboard()
{
return (this.hideSoftKeyboard);
}
/*******************************************************************************
** Setter for hideSoftKeyboard
*******************************************************************************/
public void setHideSoftKeyboard(Boolean hideSoftKeyboard)
{
this.hideSoftKeyboard = hideSoftKeyboard;
}
/*******************************************************************************
** Fluent setter for hideSoftKeyboard
*******************************************************************************/
public InputFieldValues withHideSoftKeyboard(Boolean hideSoftKeyboard)
{
this.hideSoftKeyboard = hideSoftKeyboard;
return (this);
}
}

View File

@ -30,325 +30,4 @@ import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockStyles
*******************************************************************************/
public class TextStyles implements BlockStylesInterface
{
private String color;
private String format;
private String weight;
private String size;
/***************************************************************************
**
***************************************************************************/
public enum StandardColor
{
SUCCESS,
WARNING,
ERROR,
INFO,
MUTED
}
/***************************************************************************
**
***************************************************************************/
public enum StandardFormat
{
DEFAULT,
ALERT,
BANNER
}
/***************************************************************************
**
***************************************************************************/
public enum StandardSize
{
LARGEST,
HEADLINE,
TITLE,
BODY,
SMALLEST
}
/***************************************************************************
**
***************************************************************************/
public enum StandardWeight
{
EXTRA_LIGHT("extralight"),
THIN("thin"),
MEDIUM("medium"),
SEMI_BOLD("semibold"),
BLACK("black"),
BOLD("bold"),
EXTRA_BOLD("extrabold"),
W100("100"),
W200("200"),
W300("300"),
W400("400"),
W500("500"),
W600("600"),
W700("700"),
W800("800"),
W900("900");
private final String value;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
StandardWeight(String value)
{
this.value = value;
}
/*******************************************************************************
** Getter for value
**
*******************************************************************************/
public String getValue()
{
return value;
}
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public TextStyles()
{
}
/***************************************************************************
**
***************************************************************************/
public TextStyles(StandardColor standardColor)
{
setColor(standardColor);
}
/*******************************************************************************
** Getter for format
*******************************************************************************/
public String getFormat()
{
return (this.format);
}
/*******************************************************************************
** Setter for format
*******************************************************************************/
public void setFormat(String format)
{
this.format = format;
}
/*******************************************************************************
** Fluent setter for format
*******************************************************************************/
public TextStyles withFormat(String format)
{
this.format = format;
return (this);
}
/*******************************************************************************
** Setter for format
*******************************************************************************/
public void setFormat(StandardFormat format)
{
this.format = format == null ? null : format.name().toLowerCase();
}
/*******************************************************************************
** Fluent setter for format
*******************************************************************************/
public TextStyles withFormat(StandardFormat format)
{
this.setFormat(format);
return (this);
}
/*******************************************************************************
** Getter for weight
*******************************************************************************/
public String getWeight()
{
return (this.weight);
}
/*******************************************************************************
** Setter for weight
*******************************************************************************/
public void setWeight(String weight)
{
this.weight = weight;
}
/*******************************************************************************
** Fluent setter for weight
*******************************************************************************/
public TextStyles withWeight(String weight)
{
this.weight = weight;
return (this);
}
/*******************************************************************************
** Setter for weight
*******************************************************************************/
public void setWeight(StandardWeight weight)
{
setWeight(weight == null ? null : weight.getValue());
}
/*******************************************************************************
** Fluent setter for weight
*******************************************************************************/
public TextStyles withWeight(StandardWeight weight)
{
setWeight(weight);
return (this);
}
/*******************************************************************************
** Getter for size
*******************************************************************************/
public String getSize()
{
return (this.size);
}
/*******************************************************************************
** Setter for size
*******************************************************************************/
public void setSize(String size)
{
this.size = size;
}
/*******************************************************************************
** Fluent setter for size
*******************************************************************************/
public TextStyles withSize(String size)
{
this.size = size;
return (this);
}
/*******************************************************************************
** Setter for size
*******************************************************************************/
public void setSize(StandardSize size)
{
this.size = (size == null ? null : size.name().toLowerCase());
}
/*******************************************************************************
** Fluent setter for size
*******************************************************************************/
public TextStyles withSize(StandardSize size)
{
setSize(size);
return (this);
}
/*******************************************************************************
** Getter for color
*******************************************************************************/
public String getColor()
{
return (this.color);
}
/*******************************************************************************
** Setter for color
*******************************************************************************/
public void setColor(String color)
{
this.color = color;
}
/*******************************************************************************
** Fluent setter for color
*******************************************************************************/
public TextStyles withColor(String color)
{
this.color = color;
return (this);
}
/*******************************************************************************
** Setter for color
*******************************************************************************/
public void setColor(StandardColor color)
{
this.color = color == null ? null : color.name();
}
/*******************************************************************************
** Fluent setter for color
*******************************************************************************/
public TextStyles withColor(StandardColor color)
{
setColor(color);
return (this);
}
}

View File

@ -23,7 +23,6 @@ package com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.text;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.blocks.BlockValuesInterface;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
/*******************************************************************************
@ -33,9 +32,6 @@ public class TextValues implements BlockValuesInterface
{
private String text;
private QIcon startIcon;
private QIcon endIcon;
/*******************************************************************************
@ -88,66 +84,4 @@ public class TextValues implements BlockValuesInterface
return (this);
}
/*******************************************************************************
** Getter for startIcon
*******************************************************************************/
public QIcon getStartIcon()
{
return (this.startIcon);
}
/*******************************************************************************
** Setter for startIcon
*******************************************************************************/
public void setStartIcon(QIcon startIcon)
{
this.startIcon = startIcon;
}
/*******************************************************************************
** Fluent setter for startIcon
*******************************************************************************/
public TextValues withStartIcon(QIcon startIcon)
{
this.startIcon = startIcon;
return (this);
}
/*******************************************************************************
** Getter for endIcon
*******************************************************************************/
public QIcon getEndIcon()
{
return (this.endIcon);
}
/*******************************************************************************
** Setter for endIcon
*******************************************************************************/
public void setEndIcon(QIcon endIcon)
{
this.endIcon = endIcon;
}
/*******************************************************************************
** Fluent setter for endIcon
*******************************************************************************/
public TextValues withEndIcon(QIcon endIcon)
{
this.endIcon = endIcon;
return (this);
}
}

View File

@ -89,11 +89,6 @@ public @interface QField
*******************************************************************************/
int maxLength() default Integer.MAX_VALUE;
/*******************************************************************************
**
*******************************************************************************/
int gridColumns() default -1;
/*******************************************************************************
**
*******************************************************************************/

View File

@ -154,7 +154,7 @@ public class QRecord implements Serializable
return (null);
}
Map<String, V> clone = new LinkedHashMap<>(map.size());
Map<String, V> clone = new LinkedHashMap<>();
for(Map.Entry<String, V> entry : map.entrySet())
{
Serializable value = entry.getValue();
@ -246,24 +246,6 @@ public class QRecord implements Serializable
}
/***************************************************************************
** copy all values from 'joinedRecord' into this record's values map,
** prefixing field names with joinTableNam + "."
***************************************************************************/
public void addJoinedRecordValues(String joinTableName, QRecord joinedRecord)
{
if(joinedRecord == null)
{
return;
}
for(Map.Entry<String, Serializable> entry : joinedRecord.getValues().entrySet())
{
setValue(joinTableName + "." + entry.getKey(), entry.getValue());
}
}
/*******************************************************************************
**

View File

@ -41,14 +41,11 @@ import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
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.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 static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -64,11 +61,6 @@ public abstract class QRecordEntity
private Map<String, Serializable> originalRecordValues;
////////////////////////////////////////////////////////////////////////////////
// map of entity class names to QTableMetaData objects that they helped build //
////////////////////////////////////////////////////////////////////////////////
private static Map<String, QTableMetaData> tableReferences = new HashMap<>();
/*******************************************************************************
@ -103,19 +95,6 @@ public abstract class QRecordEntity
/***************************************************************************
** register a mapping between an entity class and a table that it is associated with.
***************************************************************************/
public static void registerTable(Class<? extends QRecordEntity> entityClass, QTableMetaData table)
{
if(entityClass != null && table != null)
{
tableReferences.put(entityClass.getName(), table);
}
}
/*******************************************************************************
** Build an entity of this QRecord type from a QRecord
**
@ -197,10 +176,7 @@ public abstract class QRecordEntity
/*******************************************************************************
** Convert this entity to a QRecord. ALL fields in the entity will be set
** in the QRecord. Note that, if you're using this for an input to the UpdateAction,
** that this could cause values to be set to null, e.g., if you constructed
** a entity from scratch, and didn't set all values in it!!
** Convert this entity to a QRecord.
**
*******************************************************************************/
public QRecord toQRecord() throws QRuntimeException
@ -214,7 +190,25 @@ public abstract class QRecordEntity
qRecord.setValue(qRecordEntityField.getFieldName(), (Serializable) qRecordEntityField.getGetter().invoke(this));
}
toQRecordProcessAssociations(qRecord, (entity) -> entity.toQRecord());
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
{
@SuppressWarnings("unchecked")
List<? extends QRecordEntity> associatedEntities = (List<? extends QRecordEntity>) qRecordEntityAssociation.getGetter().invoke(this);
String associationName = qRecordEntityAssociation.getAssociationAnnotation().name();
if(associatedEntities != null)
{
/////////////////////////////////////////////////////////////////////////////////
// do this so an empty list in the entity becomes an empty list in the QRecord //
/////////////////////////////////////////////////////////////////////////////////
qRecord.withAssociatedRecords(associationName, new ArrayList<>());
}
for(QRecordEntity associatedEntity : CollectionUtils.nonNullList(associatedEntities))
{
qRecord.withAssociatedRecord(associationName, associatedEntity.toQRecord());
}
}
return (qRecord);
}
@ -226,65 +220,15 @@ public abstract class QRecordEntity
/***************************************************************************
*
***************************************************************************/
private void toQRecordProcessAssociations(QRecord outputRecord, Function<QRecordEntity, QRecord> toRecordFunction) throws Exception
{
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
{
@SuppressWarnings("unchecked")
List<? extends QRecordEntity> associatedEntities = (List<? extends QRecordEntity>) qRecordEntityAssociation.getGetter().invoke(this);
String associationName = qRecordEntityAssociation.getAssociationAnnotation().name();
if(associatedEntities != null)
{
outputRecord.withAssociatedRecords(associationName, new ArrayList<>());
for(QRecordEntity associatedEntity : associatedEntities)
{
outputRecord.withAssociatedRecord(associationName, toRecordFunction.apply(associatedEntity));
}
}
}
}
/*******************************************************************************
** Overload of toQRecordOnlyChangedFields that preserves original behavior of
** that method, which is, to NOT includePrimaryKey
**
*******************************************************************************/
@Deprecated(since = "includePrimaryKey param was added")
public QRecord toQRecordOnlyChangedFields()
{
return toQRecordOnlyChangedFields(false);
}
/*******************************************************************************
** Useful for the use-case of:
** - fetch a QRecord (e.g., QueryAction or GetAction)
** - build a QRecordEntity out of it
** - change a field (or two) in it
** - want to pass it into an UpdateAction, and want to see only the fields that
** you know you changed get passed in to UpdateAction (e.g., PATCH semantics).
**
** But also - per the includePrimaryKey param, include the primaryKey in the
** records (e.g., to tell the Update which records to update).
**
** Also, useful for:
** - construct new entity, calling setters to populate some fields
** - pass that entity into
*******************************************************************************/
public QRecord toQRecordOnlyChangedFields(boolean includePrimaryKey)
{
try
{
QRecord qRecord = new QRecord();
String primaryKeyFieldName = ObjectUtils.tryElse(() -> tableReferences.get(getClass().getName()).getPrimaryKeyField(), null);
for(QRecordEntityField qRecordEntityField : getFieldList(this.getClass()))
{
Serializable thisValue = (Serializable) qRecordEntityField.getGetter().invoke(this);
@ -294,16 +238,31 @@ public abstract class QRecordEntity
originalValue = originalRecordValues.get(qRecordEntityField.getFieldName());
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if this value and the original value don't match - OR - this is the table's primary key field - then put the value in the record. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(!Objects.equals(thisValue, originalValue) || (includePrimaryKey && Objects.equals(primaryKeyFieldName, qRecordEntityField.getFieldName())))
if(!Objects.equals(thisValue, originalValue))
{
qRecord.setValue(qRecordEntityField.getFieldName(), thisValue);
}
}
toQRecordProcessAssociations(qRecord, (entity) -> entity.toQRecordOnlyChangedFields(includePrimaryKey));
for(QRecordEntityAssociation qRecordEntityAssociation : getAssociationList(this.getClass()))
{
@SuppressWarnings("unchecked")
List<? extends QRecordEntity> associatedEntities = (List<? extends QRecordEntity>) qRecordEntityAssociation.getGetter().invoke(this);
String associationName = qRecordEntityAssociation.getAssociationAnnotation().name();
if(associatedEntities != null)
{
/////////////////////////////////////////////////////////////////////////////////
// do this so an empty list in the entity becomes an empty list in the QRecord //
/////////////////////////////////////////////////////////////////////////////////
qRecord.withAssociatedRecords(associationName, new ArrayList<>());
}
for(QRecordEntity associatedEntity : CollectionUtils.nonNullList(associatedEntities))
{
qRecord.withAssociatedRecord(associationName, associatedEntity.toQRecord());
}
}
return (qRecord);
}
@ -529,16 +488,15 @@ public abstract class QRecordEntity
{
// todo - more types!!
return (returnType.equals(String.class)
|| returnType.equals(Integer.class)
|| returnType.equals(Long.class)
|| returnType.equals(int.class)
|| returnType.equals(Boolean.class)
|| returnType.equals(boolean.class)
|| returnType.equals(BigDecimal.class)
|| returnType.equals(Instant.class)
|| returnType.equals(LocalDate.class)
|| returnType.equals(LocalTime.class)
|| returnType.equals(byte[].class));
|| returnType.equals(Integer.class)
|| returnType.equals(int.class)
|| returnType.equals(Boolean.class)
|| returnType.equals(boolean.class)
|| returnType.equals(BigDecimal.class)
|| returnType.equals(Instant.class)
|| returnType.equals(LocalDate.class)
|| returnType.equals(LocalTime.class)
|| returnType.equals(byte[].class));
/////////////////////////////////////////////
// note - this list has implications upon: //
// - QFieldType.fromClass //

View File

@ -1,201 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.data;
import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiFunction;
/*******************************************************************************
** Extension on QRecord, intended to be used where you've got records from
** multiple tables, and you want to combine them into a single "wide" joined
** record - but to do so without copying or modifying any of the individual
** records.
**
** e.g., given:
** - Order (id, orderNo, orderDate) (main table)
** - LineItem (id, sku, quantity)
** - Extrinsic (id, key, value)
**
** If set up in here as:
** - new QRecordWithJoinedRecords(order)
** .withJoinedRecordValues(lineItem)
** .withJoinedRecordValues(extrinsic)
**
** Then we'd have the appearance of values in the object like:
** - id, orderNo, orderDate, lineItem.id, lineItem.sku, lineItem.quantity, extrinsic.id, extrinsic.key, extrinsic.value
**
** Which, by the by, is how a query that returns joined records looks, and, is
** what BackendQueryFilterUtils can use to do filter.
**
** This is done without copying or mutating any of the records (which, if you just use
** QRecord.withJoinedRecordValues, then those values are copied into the main record)
** - because this object is just storing references to the input records.
**
** Note that this implies that, values changed in this record (e.g, calls to setValue)
** WILL impact the underlying records!
*******************************************************************************/
public class QRecordWithJoinedRecords extends QRecord
{
private QRecord mainRecord;
private Map<String, QRecord> components = new LinkedHashMap<>();
/***************************************************************************
**
***************************************************************************/
public QRecordWithJoinedRecords(QRecord mainRecord)
{
this.mainRecord = mainRecord;
}
/*************************************************************************
**
***************************************************************************/
@Override
public void addJoinedRecordValues(String joinTableName, QRecord joinedRecord)
{
components.put(joinTableName, joinedRecord);
}
/*************************************************************************
**
***************************************************************************/
public QRecordWithJoinedRecords withJoinedRecordValues(QRecord record, String joinTableName)
{
addJoinedRecordValues(joinTableName, record);
return (this);
}
/***************************************************************************
**
***************************************************************************/
@Override
public Serializable getValue(String fieldName)
{
return performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) -> record.getValue(f)));
}
/***************************************************************************
*
***************************************************************************/
@Override
public void setValue(String fieldName, Object value)
{
performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) ->
{
record.setValue(f, value);
return (null);
}));
}
/***************************************************************************
*
***************************************************************************/
@Override
public void setValue(String fieldName, Serializable value)
{
performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) ->
{
record.setValue(f, value);
return (null);
}));
}
/***************************************************************************
**
***************************************************************************/
@Override
public void removeValue(String fieldName)
{
performFunctionOnRecordBasedOnFieldName(fieldName, ((record, f) ->
{
record.removeValue(f);
return (null);
}));
}
/***************************************************************************
** avoid having this same block in all the functions that call it...
** given a fieldName, which may be a joinTable.fieldName, apply the function
** to the right entity.
***************************************************************************/
private Serializable performFunctionOnRecordBasedOnFieldName(String fieldName, BiFunction<QRecord, String, Serializable> functionToPerform)
{
if(fieldName.contains("."))
{
String[] parts = fieldName.split("\\.");
QRecord component = components.get(parts[0]);
if(component != null)
{
return functionToPerform.apply(component, parts[1]);
}
else
{
return null;
}
}
else
{
return functionToPerform.apply(mainRecord, fieldName);
}
}
/***************************************************************************
**
***************************************************************************/
@Override
public Map<String, Serializable> getValues()
{
Map<String, Serializable> rs = new LinkedHashMap<>(mainRecord.getValues());
for(Map.Entry<String, QRecord> componentEntry : components.entrySet())
{
String joinTableName = componentEntry.getKey();
QRecord componentRecord = componentEntry.getValue();
for(Map.Entry<String, Serializable> entry : componentRecord.getValues().entrySet())
{
rs.put(joinTableName + "." + entry.getKey(), entry.getValue());
}
}
return (rs);
}
}

View File

@ -1,47 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata;
import com.kingsrook.qqq.backend.core.logging.QLogger;
/*******************************************************************************
** for use-cases where a metaDataProducer directly adds its objects to the
** qInstance, then this empty object can be returned.
*******************************************************************************/
public class EmptyMetaDataProducerOutput implements MetaDataProducerOutput
{
private static final QLogger LOG = QLogger.getLogger(EmptyMetaDataProducerOutput.class);
/***************************************************************************
**
***************************************************************************/
@Override
public void addSelfToInstance(QInstance instance)
{
/////////////////////////////////
// noop - this output is empty //
/////////////////////////////////
LOG.trace("empty meta data producer has nothing to add.");
}
}

View File

@ -22,9 +22,12 @@
package com.kingsrook.qqq.backend.core.model.metadata;
import com.kingsrook.qqq.backend.core.model.MetaDataProducerInterface;
/*******************************************************************************
** Abstract class that knows how to produce meta data objects. Useful with
** MetaDataProducerHelper, to point at a package full of these, and populate
** MetaDataProducerHelper, to put point at a package full of these, and populate
** your whole QInstance.
*******************************************************************************/
public abstract class MetaDataProducer<T extends MetaDataProducerOutput> implements MetaDataProducerInterface<T>

View File

@ -22,37 +22,22 @@
package com.kingsrook.qqq.backend.core.model.metadata;
import java.io.Serializable;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
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.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
import com.kingsrook.qqq.backend.core.model.metadata.producers.ChildJoinFromRecordEntityGenericMetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.producers.ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.producers.PossibleValueSourceOfEnumGenericMetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.producers.PossibleValueSourceOfTableGenericMetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.producers.RecordEntityToTableGenericMetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildRecordListWidget;
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.ChildTable;
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingEntity;
import com.kingsrook.qqq.backend.core.model.metadata.producers.annotations.QMetaDataProducingPossibleValueEnum;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.ClassPathUtils;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
@ -66,6 +51,8 @@ public class MetaDataProducerHelper
private static Map<Class<?>, Integer> comparatorValuesByType = new HashMap<>();
private static Integer defaultComparatorValue;
private static ImmutableSet<ClassPath.ClassInfo> topLevelClasses;
static
{
////////////////////////////////////////////////////////////////////////////////////////
@ -86,27 +73,6 @@ public class MetaDataProducerHelper
comparatorValuesByType.put(QAppMetaData.class, 23);
}
private static MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer = null;
/*******************************************************************************
** Recursively find all classes in the given package, that implement MetaDataProducerInterface
** run them, and add their output to the given qInstance - using the provided
** tableMetaDataCustomizer to help with all RecordEntity's that
** are configured to make tables.
**
** Note - they'll be sorted by the sortOrder they provide.
*******************************************************************************/
public static void processAllMetaDataProducersInPackage(QInstance instance, String packageName, MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer) throws QException
{
MetaDataProducerHelper.tableMetaDataCustomizer = tableMetaDataCustomizer;
processAllMetaDataProducersInPackage(instance, packageName);
MetaDataProducerHelper.tableMetaDataCustomizer = null;
}
/*******************************************************************************
** Recursively find all classes in the given package, that implement MetaDataProducerInterface
** run them, and add their output to the given qInstance.
@ -121,7 +87,7 @@ public class MetaDataProducerHelper
////////////////////////////////////////////////////////////////////////
// find all the meta data producer classes in (and under) the package //
////////////////////////////////////////////////////////////////////////
classesInPackage = ClassPathUtils.getClassesInPackage(packageName);
classesInPackage = getClassesInPackage(packageName);
}
catch(Exception e)
{
@ -129,9 +95,6 @@ public class MetaDataProducerHelper
}
List<MetaDataProducerInterface<?>> producers = new ArrayList<>();
////////////////////////////////////////////////////////////////////////////////////////
// loop over classes, processing them based on either their type or their annotations //
////////////////////////////////////////////////////////////////////////////////////////
for(Class<?> aClass : classesInPackage)
{
try
@ -141,36 +104,25 @@ public class MetaDataProducerHelper
continue;
}
/////////////////////////////////////////////////////////////////////
// handle classes which are themselves MetaDataProducerInterface's //
/////////////////////////////////////////////////////////////////////
if(MetaDataProducerInterface.class.isAssignableFrom(aClass))
{
CollectionUtils.addIfNotNull(producers, processMetaDataProducer(aClass));
}
/////////////////////////////////////////////////////////////////////////
// handle classes that have the @QMetaDataProducingEntity annotation - //
// record entities that should produce meta-data //
/////////////////////////////////////////////////////////////////////////
if(aClass.isAnnotationPresent(QMetaDataProducingEntity.class))
{
producers.addAll(processMetaDataProducingEntity(aClass));
}
//////////////////////////////////////////////////////////////////
// handle classes with the @QMetaDataProducingPossibleValueEnum //
// enums that are PVS's //
//////////////////////////////////////////////////////////////////
if(aClass.isAnnotationPresent(QMetaDataProducingPossibleValueEnum.class))
{
QMetaDataProducingPossibleValueEnum qMetaDataProducingPossibleValueEnum = aClass.getAnnotation(QMetaDataProducingPossibleValueEnum.class);
if(qMetaDataProducingPossibleValueEnum.producePossibleValueSource())
boolean foundValidConstructor = false;
for(Constructor<?> constructor : aClass.getConstructors())
{
CollectionUtils.addIfNotNull(producers, processMetaDataProducingPossibleValueEnum(aClass));
if(constructor.getParameterCount() == 0)
{
Object o = constructor.newInstance();
producers.add((MetaDataProducerInterface<?>) o);
foundValidConstructor = true;
break;
}
}
if(!foundValidConstructor)
{
LOG.warn("Found a class which implements MetaDataProducerInterface, but it does not have a no-arg constructor, so it cannot be used.", logPair("class", aClass.getSimpleName()));
}
}
}
catch(Exception e)
{
@ -221,253 +173,54 @@ public class MetaDataProducerHelper
LOG.debug("Not using producer which is not enabled", logPair("producer", producer.getClass().getSimpleName()));
}
}
}
/***************************************************************************
**
***************************************************************************/
@SuppressWarnings("unchecked")
private static <T extends Serializable & PossibleValueEnum<T>> MetaDataProducerInterface<?> processMetaDataProducingPossibleValueEnum(Class<?> aClass)
{
String warningPrefix = "Found a class annotated as @" + QMetaDataProducingPossibleValueEnum.class.getSimpleName();
if(!PossibleValueEnum.class.isAssignableFrom(aClass))
{
LOG.warn(warningPrefix + ", but which is not a " + PossibleValueEnum.class.getSimpleName() + ", so it will not be used.", logPair("class", aClass.getSimpleName()));
return null;
}
PossibleValueEnum<?>[] values = (PossibleValueEnum<?>[]) aClass.getEnumConstants();
return (new PossibleValueSourceOfEnumGenericMetaDataProducer<T>(aClass.getSimpleName(), (PossibleValueEnum<T>[]) values));
}
/***************************************************************************
**
***************************************************************************/
private static List<MetaDataProducerInterface<?>> processMetaDataProducingEntity(Class<?> aClass) throws Exception
{
List<MetaDataProducerInterface<?>> rs = new ArrayList<>();
QMetaDataProducingEntity qMetaDataProducingEntity = aClass.getAnnotation(QMetaDataProducingEntity.class);
String warningPrefix = "Found a class annotated as @" + QMetaDataProducingEntity.class.getSimpleName();
///////////////////////////////////////////////////////////
// make sures class is QRecordEntity and cast it as such //
///////////////////////////////////////////////////////////
if(!QRecordEntity.class.isAssignableFrom(aClass))
{
LOG.warn(warningPrefix + ", but which is not a " + QRecordEntity.class.getSimpleName() + ", so it will not be used.", logPair("class", aClass.getSimpleName()));
return (rs);
}
@SuppressWarnings("unchecked") // safe per the check above.
Class<? extends QRecordEntity> recordEntityClass = (Class<? extends QRecordEntity>) aClass;
////////////////////////////////////////////////
// get TABLE_NAME static field from the class //
////////////////////////////////////////////////
Field tableNameField = aClass.getDeclaredField("TABLE_NAME");
if(!tableNameField.getType().equals(String.class))
{
LOG.warn(warningPrefix + ", but whose TABLE_NAME field is not a String, so it will not be used.", logPair("class", aClass.getSimpleName()));
return (rs);
}
String tableNameValue = (String) tableNameField.get(null);
//////////////////////////////////////////
// add table producer, if so configured //
//////////////////////////////////////////
if(qMetaDataProducingEntity.produceTableMetaData())
{
try
{
Class<? extends MetaDataCustomizerInterface<?>> genericMetaProductionCustomizer = (Class<? extends MetaDataCustomizerInterface<?>>) qMetaDataProducingEntity.tableMetaDataCustomizer();
Class<? extends MetaDataCustomizerInterface<QTableMetaData>> tableMetaDataProductionCustomizer = null;
if(!genericMetaProductionCustomizer.equals(MetaDataCustomizerInterface.NoopMetaDataCustomizer.class))
{
tableMetaDataProductionCustomizer = (Class<? extends MetaDataCustomizerInterface<QTableMetaData>>) genericMetaProductionCustomizer;
}
RecordEntityToTableGenericMetaDataProducer producer = new RecordEntityToTableGenericMetaDataProducer(tableNameValue, recordEntityClass, tableMetaDataProductionCustomizer);
if(tableMetaDataCustomizer != null)
{
producer.addRecordEntityTableMetaDataProductionCustomizer(tableMetaDataCustomizer);
}
rs.add(producer);
}
catch(Exception e)
{
throw new QException("Error processing table meta data producer for entity class: " + recordEntityClass.getName(), e);
}
}
////////////////////////////////////////
// add PVS producer, if so configured //
////////////////////////////////////////
if(qMetaDataProducingEntity.producePossibleValueSource())
{
rs.add(new PossibleValueSourceOfTableGenericMetaDataProducer(tableNameValue));
}
//////////////////////////
// process child tables //
//////////////////////////
for(ChildTable childTable : qMetaDataProducingEntity.childTables())
{
Class<? extends QRecordEntity> childEntityClass = childTable.childTableEntityClass();
if(childTable.childJoin().enabled())
{
CollectionUtils.addIfNotNull(rs, processChildJoin(aClass, childTable));
if(childTable.childRecordListWidget().enabled())
{
CollectionUtils.addIfNotNull(rs, processChildRecordListWidget(aClass, childTable));
}
}
else
{
if(childTable.childRecordListWidget().enabled())
{
//////////////////////////////////////////////////////////////////////////
// if not doing the join, can't do the child-widget, so warn about that //
//////////////////////////////////////////////////////////////////////////
LOG.warn(warningPrefix + " requested to produce a ChildRecordListWidget, but not produce a Join - which is not allowed (must do join to do widget). ", logPair("class", aClass.getSimpleName()), logPair("childEntityClass", childEntityClass.getSimpleName()));
}
}
}
return (rs);
}
/***************************************************************************
**
***************************************************************************/
private static MetaDataProducerInterface<?> processChildRecordListWidget(Class<?> aClass, ChildTable childTable) throws Exception
{
Class<? extends QRecordEntity> childEntityClass = childTable.childTableEntityClass();
String parentTableName = getTableNameStaticFieldValue(aClass);
String childTableName = getTableNameStaticFieldValue(childEntityClass);
ChildRecordListWidget childRecordListWidget = childTable.childRecordListWidget();
return (new ChildRecordListWidgetFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, childRecordListWidget));
}
/***************************************************************************
**
***************************************************************************/
private static String findPossibleValueField(Class<? extends QRecordEntity> entityClass, String possibleValueSourceName)
{
for(Field field : entityClass.getDeclaredFields())
{
if(field.isAnnotationPresent(QField.class))
{
QField qField = field.getAnnotation(QField.class);
if(qField.possibleValueSourceName().equals(possibleValueSourceName))
{
return field.getName();
}
}
}
return (null);
}
/***************************************************************************
**
***************************************************************************/
private static MetaDataProducerInterface<?> processChildJoin(Class<?> aClass, ChildTable childTable) throws Exception
{
Class<? extends QRecordEntity> childEntityClass = childTable.childTableEntityClass();
String parentTableName = getTableNameStaticFieldValue(aClass);
String childTableName = getTableNameStaticFieldValue(childEntityClass);
String possibleValueFieldName = findPossibleValueField(childEntityClass, parentTableName);
if(!StringUtils.hasContent(possibleValueFieldName))
{
LOG.warn("Could not find field in [" + childEntityClass.getSimpleName() + "] with possibleValueSource referencing table [" + aClass.getSimpleName() + "]");
return (null);
}
return (new ChildJoinFromRecordEntityGenericMetaDataProducer(childTableName, parentTableName, possibleValueFieldName));
}
/***************************************************************************
**
***************************************************************************/
private static MetaDataProducerInterface<?> processMetaDataProducer(Class<?> aClass) throws Exception
{
for(Constructor<?> constructor : aClass.getConstructors())
{
if(constructor.getParameterCount() == 0)
{
Object o = constructor.newInstance();
return (MetaDataProducerInterface<?>) o;
}
}
LOG.warn("Found a class which implements MetaDataProducerInterface, but it does not have a no-arg constructor, so it cannot be used.", logPair("class", aClass.getSimpleName()));
return null;
}
/***************************************************************************
**
***************************************************************************/
private static String getTableNameStaticFieldValue(Class<?> aClass) throws NoSuchFieldException, IllegalAccessException
{
Field tableNameField = aClass.getDeclaredField("TABLE_NAME");
if(!tableNameField.getType().equals(String.class))
{
return (null);
}
String tableNameValue = (String) tableNameField.get(null);
return (tableNameValue);
}
/*******************************************************************************
** Getter for tableMetaDataCustomizer
** from https://stackoverflow.com/questions/520328/can-you-find-all-classes-in-a-package-using-reflection
** (since the original, from ChatGPT, didn't work in jars, despite GPT hallucinating that it would)
*******************************************************************************/
public MetaDataCustomizerInterface<QTableMetaData> getTableMetaDataCustomizer()
private static List<Class<?>> getClassesInPackage(String packageName) throws IOException
{
return (MetaDataProducerHelper.tableMetaDataCustomizer);
List<Class<?>> classes = new ArrayList<>();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
for(ClassPath.ClassInfo info : getTopLevelClasses(loader))
{
if(info.getName().startsWith(packageName))
{
classes.add(info.load());
}
}
return (classes);
}
/*******************************************************************************
** Setter for tableMetaDataCustomizer
**
*******************************************************************************/
public void setTableMetaDataCustomizer(MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer)
private static ImmutableSet<ClassPath.ClassInfo> getTopLevelClasses(ClassLoader loader) throws IOException
{
MetaDataProducerHelper.tableMetaDataCustomizer = tableMetaDataCustomizer;
if(topLevelClasses == null)
{
topLevelClasses = ClassPath.from(loader).getTopLevelClasses();
}
return (topLevelClasses);
}
/*******************************************************************************
** Fluent setter for tableMetaDataCustomizer
**
*******************************************************************************/
public void withTableMetaDataCustomizer(MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer)
public static void clearTopLevelClassCache()
{
MetaDataProducerHelper.tableMetaDataCustomizer = tableMetaDataCustomizer;
topLevelClasses = null;
}
}

View File

@ -26,13 +26,8 @@ import java.util.HashSet;
import java.util.Set;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
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.metadata.serialization.QBackendMetaDataDeserializer;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.variants.BackendVariantsConfig;
import com.kingsrook.qqq.backend.core.model.metadata.variants.LegacyBackendVariantSetting;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
@ -50,18 +45,21 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
private Set<Capability> enabledCapabilities = new HashSet<>();
private Set<Capability> disabledCapabilities = new HashSet<>();
private Boolean usesVariants = false;
private BackendVariantsConfig backendVariantsConfig;
private Boolean usesVariants = false;
private String variantOptionsTableIdField;
private String variantOptionsTableNameField;
private String variantOptionsTableTypeField;
private String variantOptionsTableTypeValue;
private String variantOptionsTableUsernameField;
private String variantOptionsTablePasswordField;
private String variantOptionsTableApiKeyField;
private String variantOptionsTableClientIdField;
private String variantOptionsTableClientSecretField;
private String variantOptionsTableName;
// todo - at some point, we may want to apply this to secret properties on subclasses?
// @JsonFilter("secretsFilter")
@Deprecated(since = "Replaced by filter in backendVariantsConfig - but leaving as field to pair with ...TypeValue for building filter")
private String variantOptionsTableTypeField; // a field on which to filter the variant-options table, to limit which records in it are available as variants
@Deprecated(since = "Replaced by variantTypeKey and value in filter in backendVariantsConfig - but leaving as field to pair with ...TypeField for building filter")
private String variantOptionsTableTypeValue; // value for the type-field, to limit which records in it are available as variants; but also, the key in the session.backendVariants map!
/*******************************************************************************
@ -396,15 +394,22 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Getter for variantOptionsTableIdField
*******************************************************************************/
public String getVariantOptionsTableIdField()
{
return (this.variantOptionsTableIdField);
}
/*******************************************************************************
** Setter for variantOptionsTableIdField
*******************************************************************************/
@Deprecated(since = "backendVariantsConfig will infer this from the variant options table's primary key")
public void setVariantOptionsTableIdField(String variantOptionsTableIdField)
{
/////////////////////////////////////////////////
// noop as we migrate to backendVariantsConfig //
/////////////////////////////////////////////////
this.variantOptionsTableIdField = variantOptionsTableIdField;
}
@ -412,24 +417,30 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for variantOptionsTableIdField
*******************************************************************************/
@Deprecated(since = "backendVariantsConfig will infer this from the variant options table's primary key")
public QBackendMetaData withVariantOptionsTableIdField(String variantOptionsTableIdField)
{
this.setVariantOptionsTableIdField(variantOptionsTableIdField);
this.variantOptionsTableIdField = variantOptionsTableIdField;
return (this);
}
/*******************************************************************************
** Getter for variantOptionsTableNameField
*******************************************************************************/
public String getVariantOptionsTableNameField()
{
return (this.variantOptionsTableNameField);
}
/*******************************************************************************
** Setter for variantOptionsTableNameField
*******************************************************************************/
@Deprecated(since = "backendVariantsConfig will infer this from the variant options table's recordLabel")
public void setVariantOptionsTableNameField(String variantOptionsTableNameField)
{
/////////////////////////////////////////////////
// noop as we migrate to backendVariantsConfig //
/////////////////////////////////////////////////
this.variantOptionsTableNameField = variantOptionsTableNameField;
}
@ -437,26 +448,30 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for variantOptionsTableNameField
*******************************************************************************/
@Deprecated(since = "backendVariantsConfig will infer this from the variant options table's recordLabel")
public QBackendMetaData withVariantOptionsTableNameField(String variantOptionsTableNameField)
{
this.setVariantOptionsTableNameField(variantOptionsTableNameField);
this.variantOptionsTableNameField = variantOptionsTableNameField;
return (this);
}
/*******************************************************************************
** Getter for variantOptionsTableTypeField
*******************************************************************************/
public String getVariantOptionsTableTypeField()
{
return (this.variantOptionsTableTypeField);
}
/*******************************************************************************
** Setter for variantOptionsTableTypeField
*******************************************************************************/
@Deprecated(since = "Replaced by fieldName in filter in backendVariantsConfig - but leaving as field to pair with ...TypeValue for building filter")
public void setVariantOptionsTableTypeField(String variantOptionsTableTypeField)
{
this.variantOptionsTableTypeField = variantOptionsTableTypeField;
if(this.variantOptionsTableTypeValue != null)
{
this.getOrWithNewBackendVariantsConfig().setOptionsFilter(new QQueryFilter(new QFilterCriteria(variantOptionsTableTypeField, QCriteriaOperator.EQUALS, variantOptionsTableTypeValue)));
}
}
@ -464,28 +479,30 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for variantOptionsTableTypeField
*******************************************************************************/
@Deprecated(since = "Replaced by fieldName in filter in backendVariantsConfig - but leaving as field to pair with ...TypeValue for building filter")
public QBackendMetaData withVariantOptionsTableTypeField(String variantOptionsTableTypeField)
{
this.setVariantOptionsTableTypeField(variantOptionsTableTypeField);
this.variantOptionsTableTypeField = variantOptionsTableTypeField;
return (this);
}
/*******************************************************************************
** Getter for variantOptionsTableTypeValue
*******************************************************************************/
public String getVariantOptionsTableTypeValue()
{
return (this.variantOptionsTableTypeValue);
}
/*******************************************************************************
** Setter for variantOptionsTableTypeValue
*******************************************************************************/
@Deprecated(since = "Replaced by variantTypeKey and value in filter in backendVariantsConfig - but leaving as field to pair with ...TypeField for building filter")
public void setVariantOptionsTableTypeValue(String variantOptionsTableTypeValue)
{
this.getOrWithNewBackendVariantsConfig().setVariantTypeKey(variantOptionsTableTypeValue);
this.variantOptionsTableTypeValue = variantOptionsTableTypeValue;
if(this.variantOptionsTableTypeField != null)
{
this.getOrWithNewBackendVariantsConfig().setOptionsFilter(new QQueryFilter(new QFilterCriteria(variantOptionsTableTypeField, QCriteriaOperator.EQUALS, variantOptionsTableTypeValue)));
}
}
@ -493,22 +510,30 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for variantOptionsTableTypeValue
*******************************************************************************/
@Deprecated(since = "Replaced by variantTypeKey and value in filter in backendVariantsConfig - but leaving as field to pair with ...TypeField for building filter")
public QBackendMetaData withVariantOptionsTableTypeValue(String variantOptionsTableTypeValue)
{
this.setVariantOptionsTableTypeValue(variantOptionsTableTypeValue);
this.variantOptionsTableTypeValue = variantOptionsTableTypeValue;
return (this);
}
/*******************************************************************************
** Getter for variantOptionsTableUsernameField
*******************************************************************************/
public String getVariantOptionsTableUsernameField()
{
return (this.variantOptionsTableUsernameField);
}
/*******************************************************************************
** Setter for variantOptionsTableUsernameField
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
public void setVariantOptionsTableUsernameField(String variantOptionsTableUsernameField)
{
this.getOrWithNewBackendVariantsConfig().withBackendSettingSourceFieldName(LegacyBackendVariantSetting.USERNAME, variantOptionsTableUsernameField);
this.variantOptionsTableUsernameField = variantOptionsTableUsernameField;
}
@ -516,22 +541,30 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for variantOptionsTableUsernameField
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
public QBackendMetaData withVariantOptionsTableUsernameField(String variantOptionsTableUsernameField)
{
this.setVariantOptionsTableUsernameField(variantOptionsTableUsernameField);
this.variantOptionsTableUsernameField = variantOptionsTableUsernameField;
return (this);
}
/*******************************************************************************
** Getter for variantOptionsTablePasswordField
*******************************************************************************/
public String getVariantOptionsTablePasswordField()
{
return (this.variantOptionsTablePasswordField);
}
/*******************************************************************************
** Setter for variantOptionsTablePasswordField
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
public void setVariantOptionsTablePasswordField(String variantOptionsTablePasswordField)
{
this.getOrWithNewBackendVariantsConfig().withBackendSettingSourceFieldName(LegacyBackendVariantSetting.PASSWORD, variantOptionsTablePasswordField);
this.variantOptionsTablePasswordField = variantOptionsTablePasswordField;
}
@ -539,22 +572,30 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for variantOptionsTablePasswordField
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
public QBackendMetaData withVariantOptionsTablePasswordField(String variantOptionsTablePasswordField)
{
this.setVariantOptionsTablePasswordField(variantOptionsTablePasswordField);
this.variantOptionsTablePasswordField = variantOptionsTablePasswordField;
return (this);
}
/*******************************************************************************
** Getter for variantOptionsTableApiKeyField
*******************************************************************************/
public String getVariantOptionsTableApiKeyField()
{
return (this.variantOptionsTableApiKeyField);
}
/*******************************************************************************
** Setter for variantOptionsTableApiKeyField
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
public void setVariantOptionsTableApiKeyField(String variantOptionsTableApiKeyField)
{
this.getOrWithNewBackendVariantsConfig().withBackendSettingSourceFieldName(LegacyBackendVariantSetting.API_KEY, variantOptionsTableApiKeyField);
this.variantOptionsTableApiKeyField = variantOptionsTableApiKeyField;
}
@ -562,22 +603,30 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for variantOptionsTableApiKeyField
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
public QBackendMetaData withVariantOptionsTableApiKeyField(String variantOptionsTableApiKeyField)
{
this.setVariantOptionsTableApiKeyField(variantOptionsTableApiKeyField);
this.variantOptionsTableApiKeyField = variantOptionsTableApiKeyField;
return (this);
}
/*******************************************************************************
** Getter for variantOptionsTableName
*******************************************************************************/
public String getVariantOptionsTableName()
{
return (this.variantOptionsTableName);
}
/*******************************************************************************
** Setter for variantOptionsTableName
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.tableName")
public void setVariantOptionsTableName(String variantOptionsTableName)
{
this.getOrWithNewBackendVariantsConfig().withOptionsTableName(variantOptionsTableName);
this.variantOptionsTableName = variantOptionsTableName;
}
@ -585,10 +634,9 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for variantOptionsTableName
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.tableName")
public QBackendMetaData withVariantOptionsTableName(String variantOptionsTableName)
{
this.setVariantOptionsTableName(variantOptionsTableName);
this.variantOptionsTableName = variantOptionsTableName;
return (this);
}
@ -603,15 +651,22 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
qInstance.addBackend(this);
}
/*******************************************************************************
** Getter for variantOptionsTableClientIdField
*******************************************************************************/
public String getVariantOptionsTableClientIdField()
{
return (this.variantOptionsTableClientIdField);
}
/*******************************************************************************
** Setter for variantOptionsTableClientIdField
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
public void setVariantOptionsTableClientIdField(String variantOptionsTableClientIdField)
{
this.getOrWithNewBackendVariantsConfig().withBackendSettingSourceFieldName(LegacyBackendVariantSetting.CLIENT_ID, variantOptionsTableClientIdField);
this.variantOptionsTableClientIdField = variantOptionsTableClientIdField;
}
@ -619,22 +674,30 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for variantOptionsTableClientIdField
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
public QBackendMetaData withVariantOptionsTableClientIdField(String variantOptionsTableClientIdField)
{
this.setVariantOptionsTableClientIdField(variantOptionsTableClientIdField);
this.variantOptionsTableClientIdField = variantOptionsTableClientIdField;
return (this);
}
/*******************************************************************************
** Getter for variantOptionsTableClientSecretField
*******************************************************************************/
public String getVariantOptionsTableClientSecretField()
{
return (this.variantOptionsTableClientSecretField);
}
/*******************************************************************************
** Setter for variantOptionsTableClientSecretField
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
public void setVariantOptionsTableClientSecretField(String variantOptionsTableClientSecretField)
{
this.getOrWithNewBackendVariantsConfig().withBackendSettingSourceFieldName(LegacyBackendVariantSetting.CLIENT_SECRET, variantOptionsTableClientSecretField);
this.variantOptionsTableClientSecretField = variantOptionsTableClientSecretField;
}
@ -642,55 +705,11 @@ public class QBackendMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
** Fluent setter for variantOptionsTableClientSecretField
*******************************************************************************/
@Deprecated(since = "Replaced by backendVariantsConfig.backendSettingSourceFieldNameMap")
public QBackendMetaData withVariantOptionsTableClientSecretField(String variantOptionsTableClientSecretField)
{
this.setVariantOptionsTableClientSecretField(variantOptionsTableClientSecretField);
this.variantOptionsTableClientSecretField = variantOptionsTableClientSecretField;
return (this);
}
/*******************************************************************************
** Getter for backendVariantsConfig
*******************************************************************************/
public BackendVariantsConfig getBackendVariantsConfig()
{
return (this.backendVariantsConfig);
}
/*******************************************************************************
** Setter for backendVariantsConfig
*******************************************************************************/
public void setBackendVariantsConfig(BackendVariantsConfig backendVariantsConfig)
{
this.backendVariantsConfig = backendVariantsConfig;
}
/*******************************************************************************
** Fluent setter for backendVariantsConfig
*******************************************************************************/
public QBackendMetaData withBackendVariantsConfig(BackendVariantsConfig backendVariantsConfig)
{
this.backendVariantsConfig = backendVariantsConfig;
return (this);
}
/***************************************************************************
**
***************************************************************************/
private BackendVariantsConfig getOrWithNewBackendVariantsConfig()
{
if(backendVariantsConfig == null)
{
setBackendVariantsConfig(new BackendVariantsConfig());
}
return backendVariantsConfig;
}
}

View File

@ -43,7 +43,6 @@ import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
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.branding.QBrandingMetaData;
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.frontend.AppTreeNode;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.AppTreeNodeType;
@ -114,8 +113,6 @@ public class QInstance
private QPermissionRules defaultPermissionRules = QPermissionRules.defaultInstance();
private QAuditRules defaultAuditRules = QAuditRules.defaultInstanceLevelNone();
private QCodeReference metaDataFilter = 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... //
@ -1488,35 +1485,4 @@ public class QInstance
QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, listForSlot);
}
/*******************************************************************************
** Getter for metaDataFilter
*******************************************************************************/
public QCodeReference getMetaDataFilter()
{
return (this.metaDataFilter);
}
/*******************************************************************************
** Setter for metaDataFilter
*******************************************************************************/
public void setMetaDataFilter(QCodeReference metaDataFilter)
{
this.metaDataFilter = metaDataFilter;
}
/*******************************************************************************
** Fluent setter for metaDataFilter
*******************************************************************************/
public QInstance withMetaDataFilter(QCodeReference metaDataFilter)
{
this.metaDataFilter = metaDataFilter;
return (this);
}
}

View File

@ -30,5 +30,4 @@ public enum AuditLevel
NONE,
RECORD,
FIELD
// idea: only audit changes to fields, e.g., on edit. though, is that a different dimension than this?
}

View File

@ -177,7 +177,7 @@ public class QAuthenticationMetaData implements TopLevelMetaDataInterface
/*******************************************************************************
**
*******************************************************************************/
public QAuthenticationMetaData withValues(Map<String, String> values)
public QAuthenticationMetaData withVales(Map<String, String> values)
{
this.values = values;
return (this);

View File

@ -1,58 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2024. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.code;
/*******************************************************************************
** Specialized type of QCodeReference that takes a lambda function object.
**
** Originally intended for more concise setup of backend steps in tests - but,
** may be generally useful.
*******************************************************************************/
public class QCodeReferenceLambda<T> extends QCodeReference
{
private final T lambda;
/***************************************************************************
**
***************************************************************************/
public QCodeReferenceLambda(T lambda)
{
this.lambda = lambda;
this.setCodeType(QCodeType.JAVA);
this.setName("[Lambda:" + lambda.toString() + "]");
}
/*******************************************************************************
** Getter for lambda
**
*******************************************************************************/
public T getLambda()
{
return lambda;
}
}

View File

@ -30,7 +30,6 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.kingsrook.qqq.backend.core.instances.QInstanceHelpContentManager;
import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface;
import com.kingsrook.qqq.backend.core.model.dashboard.widgets.WidgetType;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.help.HelpRole;
@ -70,7 +69,6 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
protected Map<String, Serializable> defaultValues = new LinkedHashMap<>();
protected QInstanceValidatorPluginInterface<QWidgetMetaDataInterface> validatorPlugin;
/*******************************************************************************
@ -766,35 +764,4 @@ public class QWidgetMetaData implements QWidgetMetaDataInterface
QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, listForSlot);
}
/*******************************************************************************
** Getter for validatorPlugin
*******************************************************************************/
public QInstanceValidatorPluginInterface<QWidgetMetaDataInterface> getValidatorPlugin()
{
return (this.validatorPlugin);
}
/*******************************************************************************
** Setter for validatorPlugin
*******************************************************************************/
public void setValidatorPlugin(QInstanceValidatorPluginInterface<QWidgetMetaDataInterface> validatorPlugin)
{
this.validatorPlugin = validatorPlugin;
}
/*******************************************************************************
** Fluent setter for validatorPlugin
*******************************************************************************/
public QWidgetMetaData withValidatorPlugin(QInstanceValidatorPluginInterface<QWidgetMetaDataInterface> validatorPlugin)
{
this.validatorPlugin = validatorPlugin;
return (this);
}
}

View File

@ -26,7 +26,6 @@ import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.kingsrook.qqq.backend.core.instances.validation.plugins.QInstanceValidatorPluginInterface;
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.TopLevelMetaDataInterface;
@ -278,13 +277,5 @@ public interface QWidgetMetaDataInterface extends MetaDataWithPermissionRules, T
qInstance.addWidget(this);
}
/***************************************************************************
** let the widget include an instance validator plugin
***************************************************************************/
default QInstanceValidatorPluginInterface<QWidgetMetaDataInterface> getValidatorPlugin()
{
return (null);
}
}

View File

@ -41,7 +41,6 @@ public enum AdornmentType
RENDER_HTML,
REVEAL,
FILE_DOWNLOAD,
FILE_UPLOAD,
ERROR;
//////////////////////////////////////////////////////////////////////////
// keep these values in sync with AdornmentType.ts in qqq-frontend-core //
@ -69,9 +68,6 @@ public enum AdornmentType
String DEFAULT_EXTENSION = "defaultExtension";
String DEFAULT_MIME_TYPE = "defaultMimeType";
String SUPPLEMENTAL_PROCESS_NAME = "supplementalProcessName";
String SUPPLEMENTAL_CODE_REFERENCE = "supplementalCodeReference";
////////////////////////////////////////////////////
// use these two together, as in: //
// FILE_NAME_FORMAT = "Order %s Packing Slip.pdf" //
@ -168,65 +164,4 @@ public enum AdornmentType
}
}
/*******************************************************************************
**
*******************************************************************************/
public static class FileUploadAdornment
{
public static String FORMAT = "format";
public static String WIDTH = "width";
/***************************************************************************
**
***************************************************************************/
public static FieldAdornment newFieldAdornment()
{
return (new FieldAdornment(AdornmentType.FILE_UPLOAD));
}
/***************************************************************************
**
***************************************************************************/
public static Pair<String, String> formatDragAndDrop()
{
return (Pair.of(FORMAT, "dragAndDrop"));
}
/***************************************************************************
**
***************************************************************************/
public static Pair<String, String> formatButton()
{
return (Pair.of(FORMAT, "button"));
}
/***************************************************************************
**
***************************************************************************/
public static Pair<String, String> widthFull()
{
return (Pair.of(WIDTH, "full"));
}
/***************************************************************************
**
***************************************************************************/
public static Pair<String, String> widthHalf()
{
return (Pair.of(WIDTH, "half"));
}
}
}

View File

@ -177,7 +177,7 @@ public class FieldAdornment
** Fluent setter for values
**
*******************************************************************************/
public FieldAdornment withValue(Pair<String, ? extends Serializable> value)
public FieldAdornment withValue(Pair<String, Serializable> value)
{
return (withValue(value.getA(), value.getB()));
}

View File

@ -1,86 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.fields;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Wrapper (record) that holds a QFieldMetaData and a QTableMetaData -
**
** With a factory method (`get()`) to go from the use-case of, a String that's
** "joinTable.fieldName" or "fieldName" to the pair.
**
** Note that the "joinTable" member here - could be the "mainTable" passed in
** to that `get()` method.
**
*******************************************************************************/
public record FieldAndJoinTable(QFieldMetaData field, QTableMetaData joinTable)
{
/***************************************************************************
** given a table, and a field-name string (which should either be the name
** of a field on that table, or another tableName + "." + fieldName (from
** that table) - get back the pair of table & field metaData that the
** input string is talking about.
***************************************************************************/
public static FieldAndJoinTable get(QTableMetaData mainTable, String fieldName) throws QException
{
if(fieldName.indexOf('.') > -1)
{
String joinTableName = fieldName.replaceAll("\\..*", "");
String joinFieldName = fieldName.replaceAll(".*\\.", "");
QTableMetaData joinTable = QContext.getQInstance().getTable(joinTableName);
if(joinTable == null)
{
throw (new QException("Unrecognized join table name: " + joinTableName));
}
return new FieldAndJoinTable(joinTable.getField(joinFieldName), joinTable);
}
else
{
return new FieldAndJoinTable(mainTable.getField(fieldName), mainTable);
}
}
/*******************************************************************************
**
*******************************************************************************/
public String getLabel(QTableMetaData mainTable)
{
if(mainTable.getName().equals(joinTable.getName()))
{
return (field.getLabel());
}
else
{
return (joinTable.getLabel() + ": " + field.getLabel());
}
}
}

View File

@ -22,45 +22,11 @@
package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.util.List;
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;
/*******************************************************************************
** Interface to mark a field behavior as one to be used during generating
** display values.
*******************************************************************************/
public interface FieldDisplayBehavior<T extends FieldDisplayBehavior<T>> extends FieldBehavior<T>
{
NoopFieldDisplayBehavior NOOP = new NoopFieldDisplayBehavior();
/***************************************************************************
**
***************************************************************************/
@Override
@SuppressWarnings("unchecked")
default T getDefault()
{
return (T) NOOP;
}
/***************************************************************************
** a default implementation for this behavior type, which does nothing.
***************************************************************************/
class NoopFieldDisplayBehavior implements FieldDisplayBehavior<NoopFieldDisplayBehavior>
{
/***************************************************************************
**
***************************************************************************/
@Override
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
{
}
}
}

View File

@ -1,73 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.util.List;
import java.util.function.Consumer;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kingsrook.qqq.backend.core.actions.values.ValueBehaviorApplier;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.JsonUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
** Display value formatter for fields which store a QQueryFilter as JSON.
*******************************************************************************/
public class FilterJsonFieldDisplayValueFormatter implements FieldDisplayBehavior<FilterJsonFieldDisplayValueFormatter>
{
private static Consumer<ObjectMapper> jsonMapperCustomizer = om -> om.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
/*******************************************************************************
**
*******************************************************************************/
@Override
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
{
for(QRecord record : CollectionUtils.nonNullList(recordList))
{
String queryFilterJson = record.getValueString(field.getName());
if(StringUtils.hasContent(queryFilterJson))
{
try
{
QQueryFilter qQueryFilter = JsonUtils.toObject(queryFilterJson, QQueryFilter.class, jsonMapperCustomizer);
int criteriaCount = CollectionUtils.nonNullList(qQueryFilter.getCriteria()).size();
record.setDisplayValue(field.getName(), criteriaCount + " Filter" + StringUtils.plural(criteriaCount));
}
catch(Exception e)
{
record.setDisplayValue(field.getName(), "Invalid Filter...");
}
}
}
}
}

View File

@ -42,7 +42,6 @@ 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.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.StringUtils;
@ -74,14 +73,11 @@ public class QFieldMetaData implements Cloneable
// propose doing that in a secondary field, e.g., "onlyEditableOn=insert|update" //
///////////////////////////////////////////////////////////////////////////////////
private String displayFormat = "%s";
private String displayFormat = "%s";
private Serializable defaultValue;
private String possibleValueSourceName;
private QQueryFilter possibleValueSourceFilter;
private String possibleValueSourceName;
private QQueryFilter possibleValueSourceFilter;
private QPossibleValueSource inlinePossibleValueSource;
private Integer gridColumns;
private Integer maxLength;
private Set<FieldBehavior<?>> behaviors;
@ -199,7 +195,6 @@ public class QFieldMetaData implements Cloneable
setIsRequired(fieldAnnotation.isRequired());
setIsEditable(fieldAnnotation.isEditable());
setIsHidden(fieldAnnotation.isHidden());
setGridColumns(fieldAnnotation.gridColumns());
if(StringUtils.hasContent(fieldAnnotation.label()))
{
@ -1063,66 +1058,4 @@ public class QFieldMetaData implements Cloneable
QInstanceHelpContentManager.removeHelpContentByRoleSetFromList(roles, this.helpContents);
}
/*******************************************************************************
** Getter for inlinePossibleValueSource
*******************************************************************************/
public QPossibleValueSource getInlinePossibleValueSource()
{
return (this.inlinePossibleValueSource);
}
/*******************************************************************************
** Setter for inlinePossibleValueSource
*******************************************************************************/
public void setInlinePossibleValueSource(QPossibleValueSource inlinePossibleValueSource)
{
this.inlinePossibleValueSource = inlinePossibleValueSource;
}
/*******************************************************************************
** Fluent setter for inlinePossibleValueSource
*******************************************************************************/
public QFieldMetaData withInlinePossibleValueSource(QPossibleValueSource inlinePossibleValueSource)
{
this.inlinePossibleValueSource = inlinePossibleValueSource;
return (this);
}
/*******************************************************************************
** Getter for gridColumns
*******************************************************************************/
public Integer getGridColumns()
{
return (this.gridColumns);
}
/*******************************************************************************
** Setter for gridColumns
*******************************************************************************/
public void setGridColumns(Integer gridColumns)
{
this.gridColumns = gridColumns;
}
/*******************************************************************************
** Fluent setter for gridColumns
*******************************************************************************/
public QFieldMetaData withGridColumns(Integer gridColumns)
{
this.gridColumns = gridColumns;
return (this);
}
}

View File

@ -27,7 +27,6 @@ import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
@ -101,16 +100,6 @@ public enum QFieldType
/***************************************************************************
**
***************************************************************************/
public String getMixedCaseLabel()
{
return StringUtils.allCapsToMixedCase(name());
}
/*******************************************************************************
**
*******************************************************************************/
@ -131,16 +120,6 @@ public enum QFieldType
/*******************************************************************************
**
*******************************************************************************/
public boolean isTemporal()
{
return this == QFieldType.DATE || this == QFieldType.DATE_TIME || this == QFieldType.TIME;
}
/*******************************************************************************
**
*******************************************************************************/

View File

@ -1,477 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qqq.backend.core.model.metadata.fields;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
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.model.statusmessages.BadInputStatusMessage;
/*******************************************************************************
** Validate the min & max value for numeric fields.
**
** For each min & max, there are 4 possible settings:
** - value - the number that is compared.
** - allowEqualTo - defaults to true. controls if < (>) or ≤ (≥)
** - behavior - defaults to ERROR. optionally can be "CLIP" instead.
** - clipAmount - if clipping, and not allowing equalTo, how much off the limit
** value should be added or subtracted. Defaults to 1.
**
** Convenient `withMin()` and `withMax()` methods exist for setting all 4
** properties for each of min or max. Else, fluent-setters are recommended.
*******************************************************************************/
public class ValueRangeBehavior implements FieldBehavior<ValueRangeBehavior>
{
/***************************************************************************
**
***************************************************************************/
public enum Behavior
{
ERROR,
CLIP
}
private Number minValue;
private boolean minAllowEqualTo = true;
private Behavior minBehavior = Behavior.ERROR;
private BigDecimal minClipAmount = BigDecimal.ONE;
private Number maxValue;
private boolean maxAllowEqualTo = true;
private Behavior maxBehavior = Behavior.ERROR;
private BigDecimal maxClipAmount = BigDecimal.ONE;
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public ValueRangeBehavior()
{
}
/***************************************************************************
**
***************************************************************************/
@Override
public ValueRangeBehavior getDefault()
{
return null;
}
/***************************************************************************
**
***************************************************************************/
@Override
public void apply(ValueBehaviorApplier.Action action, List<QRecord> recordList, QInstance instance, QTableMetaData table, QFieldMetaData field)
{
BigDecimal minLimitBigDecimal = minValue == null ? null : new BigDecimal(minValue.toString());
String minLimitString = minValue == null ? null : minValue.toString();
BigDecimal maxLimitBigDecimal = maxValue == null ? null : new BigDecimal(maxValue.toString());
String maxLimitString = maxValue == null ? null : maxValue.toString();
for(QRecord record : recordList)
{
BigDecimal recordValue = record.getValueBigDecimal(field.getName());
if(recordValue != null)
{
if(minLimitBigDecimal != null)
{
int compare = recordValue.compareTo(minLimitBigDecimal);
if(compare < 0 || (compare == 0 && !minAllowEqualTo))
{
if(this.minBehavior == Behavior.ERROR)
{
String operator = minAllowEqualTo ? "" : "greater than ";
record.addError(new BadInputStatusMessage("The value for " + field.getLabel() + " is too small (minimum allowed value is " + operator + minLimitString + ")"));
}
else if(this.minBehavior == Behavior.CLIP)
{
if(minAllowEqualTo)
{
record.setValue(field.getName(), minLimitBigDecimal);
}
else
{
record.setValue(field.getName(), minLimitBigDecimal.add(minClipAmount));
}
}
}
}
if(maxLimitBigDecimal != null)
{
int compare = recordValue.compareTo(maxLimitBigDecimal);
if(compare > 0 || (compare == 0 && !maxAllowEqualTo))
{
if(this.maxBehavior == Behavior.ERROR)
{
String operator = maxAllowEqualTo ? "" : "less than ";
record.addError(new BadInputStatusMessage("The value for " + field.getLabel() + " is too large (maximum allowed value is " + operator + maxLimitString + ")"));
}
else if(this.maxBehavior == Behavior.CLIP)
{
if(maxAllowEqualTo)
{
record.setValue(field.getName(), maxLimitBigDecimal);
}
else
{
record.setValue(field.getName(), maxLimitBigDecimal.subtract(maxClipAmount));
}
}
}
}
}
}
}
/***************************************************************************
**
***************************************************************************/
@Override
public boolean allowMultipleBehaviorsOfThisType()
{
return (false);
}
/***************************************************************************
**
***************************************************************************/
@Override
public List<String> validateBehaviorConfiguration(QTableMetaData tableMetaData, QFieldMetaData fieldMetaData)
{
List<String> errors = new ArrayList<>();
if(minValue == null && maxValue == null)
{
errors.add("Either minValue or maxValue (or both) must be set.");
}
if(minValue != null && maxValue != null && new BigDecimal(minValue.toString()).compareTo(new BigDecimal(maxValue.toString())) > 0)
{
errors.add("minValue must be >= maxValue.");
}
if(fieldMetaData != null && fieldMetaData.getType() != null && !fieldMetaData.getType().isNumeric())
{
errors.add("can only be applied to a numeric type field.");
}
return (errors);
}
/***************************************************************************
**
***************************************************************************/
public ValueRangeBehavior withMin(Number value, boolean allowEqualTo, Behavior behavior, BigDecimal clipAmount)
{
setMinValue(value);
setMinAllowEqualTo(allowEqualTo);
setMinBehavior(behavior);
setMinClipAmount(clipAmount);
return (this);
}
/***************************************************************************
**
***************************************************************************/
public ValueRangeBehavior withMax(Number value, boolean allowEqualTo, Behavior behavior, BigDecimal clipAmount)
{
setMaxValue(value);
setMaxAllowEqualTo(allowEqualTo);
setMaxBehavior(behavior);
setMaxClipAmount(clipAmount);
return (this);
}
/*******************************************************************************
** Getter for minValue
*******************************************************************************/
public Number getMinValue()
{
return (this.minValue);
}
/*******************************************************************************
** Setter for minValue
*******************************************************************************/
public void setMinValue(Number minValue)
{
this.minValue = minValue;
}
/*******************************************************************************
** Fluent setter for minValue
*******************************************************************************/
public ValueRangeBehavior withMinValue(Number minValue)
{
this.minValue = minValue;
return (this);
}
/*******************************************************************************
** Getter for maxValue
*******************************************************************************/
public Number getMaxValue()
{
return (this.maxValue);
}
/*******************************************************************************
** Setter for maxValue
*******************************************************************************/
public void setMaxValue(Number maxValue)
{
this.maxValue = maxValue;
}
/*******************************************************************************
** Fluent setter for maxValue
*******************************************************************************/
public ValueRangeBehavior withMaxValue(Number maxValue)
{
this.maxValue = maxValue;
return (this);
}
/*******************************************************************************
** Getter for minAllowEqualTo
*******************************************************************************/
public boolean getMinAllowEqualTo()
{
return (this.minAllowEqualTo);
}
/*******************************************************************************
** Setter for minAllowEqualTo
*******************************************************************************/
public void setMinAllowEqualTo(boolean minAllowEqualTo)
{
this.minAllowEqualTo = minAllowEqualTo;
}
/*******************************************************************************
** Fluent setter for minAllowEqualTo
*******************************************************************************/
public ValueRangeBehavior withMinAllowEqualTo(boolean minAllowEqualTo)
{
this.minAllowEqualTo = minAllowEqualTo;
return (this);
}
/*******************************************************************************
** Getter for maxAllowEqualTo
*******************************************************************************/
public boolean getMaxAllowEqualTo()
{
return (this.maxAllowEqualTo);
}
/*******************************************************************************
** Setter for maxAllowEqualTo
*******************************************************************************/
public void setMaxAllowEqualTo(boolean maxAllowEqualTo)
{
this.maxAllowEqualTo = maxAllowEqualTo;
}
/*******************************************************************************
** Fluent setter for maxAllowEqualTo
*******************************************************************************/
public ValueRangeBehavior withMaxAllowEqualTo(boolean maxAllowEqualTo)
{
this.maxAllowEqualTo = maxAllowEqualTo;
return (this);
}
/*******************************************************************************
** Getter for minBehavior
*******************************************************************************/
public Behavior getMinBehavior()
{
return (this.minBehavior);
}
/*******************************************************************************
** Setter for minBehavior
*******************************************************************************/
public void setMinBehavior(Behavior minBehavior)
{
this.minBehavior = minBehavior;
}
/*******************************************************************************
** Fluent setter for minBehavior
*******************************************************************************/
public ValueRangeBehavior withMinBehavior(Behavior minBehavior)
{
this.minBehavior = minBehavior;
return (this);
}
/*******************************************************************************
** Getter for maxBehavior
*******************************************************************************/
public Behavior getMaxBehavior()
{
return (this.maxBehavior);
}
/*******************************************************************************
** Setter for maxBehavior
*******************************************************************************/
public void setMaxBehavior(Behavior maxBehavior)
{
this.maxBehavior = maxBehavior;
}
/*******************************************************************************
** Fluent setter for maxBehavior
*******************************************************************************/
public ValueRangeBehavior withMaxBehavior(Behavior maxBehavior)
{
this.maxBehavior = maxBehavior;
return (this);
}
/*******************************************************************************
** Getter for minClipAmount
*******************************************************************************/
public BigDecimal getMinClipAmount()
{
return (this.minClipAmount);
}
/*******************************************************************************
** Setter for minClipAmount
*******************************************************************************/
public void setMinClipAmount(BigDecimal minClipAmount)
{
this.minClipAmount = minClipAmount;
}
/*******************************************************************************
** Fluent setter for minClipAmount
*******************************************************************************/
public ValueRangeBehavior withMinClipAmount(BigDecimal minClipAmount)
{
this.minClipAmount = minClipAmount;
return (this);
}
/*******************************************************************************
** Getter for maxClipAmount
*******************************************************************************/
public BigDecimal getMaxClipAmount()
{
return (this.maxClipAmount);
}
/*******************************************************************************
** Setter for maxClipAmount
*******************************************************************************/
public void setMaxClipAmount(BigDecimal maxClipAmount)
{
this.maxClipAmount = maxClipAmount;
}
/*******************************************************************************
** Fluent setter for maxClipAmount
*******************************************************************************/
public ValueRangeBehavior withMaxClipAmount(BigDecimal maxClipAmount)
{
this.maxClipAmount = maxClipAmount;
return (this);
}
}

View File

@ -26,7 +26,6 @@ import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppChildMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
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;
@ -46,7 +45,7 @@ public class AppTreeNode
private String label;
private List<AppTreeNode> children;
private QIcon icon;
private String iconName;
@ -83,7 +82,7 @@ public class AppTreeNode
if(appChildMetaData.getIcon() != null)
{
// todo - propagate icons from parents, if they aren't set here...
this.icon = appChildMetaData.getIcon();
this.iconName = appChildMetaData.getIcon().getName();
}
}
@ -139,18 +138,7 @@ public class AppTreeNode
*******************************************************************************/
public String getIconName()
{
return (icon == null ? null : icon.getName());
}
/*******************************************************************************
** Getter for icon
**
*******************************************************************************/
public QIcon getIcon()
{
return icon;
return iconName;
}

View File

@ -32,7 +32,6 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.kingsrook.qqq.backend.core.model.actions.metadata.MetaDataOutput;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QSupplementalAppMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@ -46,7 +45,7 @@ public class QFrontendAppMetaData
{
private String name;
private String label;
private QIcon icon;
private String iconName;
private List<String> widgets = new ArrayList<>();
private List<AppTreeNode> children = new ArrayList<>();
@ -57,7 +56,6 @@ public class QFrontendAppMetaData
private Map<String, QSupplementalAppMetaData> supplementalAppMetaData;
/*******************************************************************************
**
*******************************************************************************/
@ -65,7 +63,11 @@ public class QFrontendAppMetaData
{
this.name = appMetaData.getName();
this.label = appMetaData.getLabel();
this.icon = appMetaData.getIcon();
if(appMetaData.getIcon() != null)
{
this.iconName = appMetaData.getIcon().getName();
}
List<String> filteredWidgets = CollectionUtils.nonNullList(appMetaData.getWidgets()).stream().filter(n -> metaDataOutput.getWidgets().containsKey(n)).toList();
if(CollectionUtils.nullSafeHasContents(filteredWidgets))
@ -79,10 +81,6 @@ public class QFrontendAppMetaData
List<String> filteredTables = CollectionUtils.nonNullList(section.getTables()).stream().filter(n -> metaDataOutput.getTables().containsKey(n)).toList();
List<String> filteredProcesses = CollectionUtils.nonNullList(section.getProcesses()).stream().filter(n -> metaDataOutput.getProcesses().containsKey(n)).toList();
List<String> filteredReports = CollectionUtils.nonNullList(section.getReports()).stream().filter(n -> metaDataOutput.getReports().containsKey(n)).toList();
//////////////////////////////////////////////////////
// only include the section if it has some contents //
//////////////////////////////////////////////////////
if(!filteredTables.isEmpty() || !filteredProcesses.isEmpty() || !filteredReports.isEmpty())
{
QAppSection clonedSection = section.clone();
@ -176,7 +174,18 @@ public class QFrontendAppMetaData
*******************************************************************************/
public String getIconName()
{
return (icon == null ? null : icon.getName());
return iconName;
}
/*******************************************************************************
** Setter for iconName
**
*******************************************************************************/
public void setIconName(String iconName)
{
this.iconName = iconName;
}
@ -226,15 +235,4 @@ public class QFrontendAppMetaData
{
return supplementalAppMetaData;
}
/*******************************************************************************
** Getter for icon
**
*******************************************************************************/
public QIcon getIcon()
{
return icon;
}
}

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