Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,17 @@ field may declare:
when the document is saved for all the lines that break it - not once per line. `{count}` in the
message is replaced with the number of lines. Always a warning; a hard per-line rule is a
`compare` on the items entity.
**A check may read a calculated field (#7544).** The row-level checks - refusing and `severity:
warn` alike - judge the record as the write will store it: a field's `calculatedOnCreate` /
`calculatedOnUpdate` expression is evaluated first (on an update, over the stored values of the
system-owned fields such as a roll-up's `paid`), so a client never has to send the computed value.
"Warn when an invoice is issued more than 5 days after its tax event" is a calculated delay plus a
soft compare on it:
`{ name: issueDelayDays, type: integer, calculatedOnCreate: "daysBetween(TaxEventDate, Date)",
calculatedOnUpdate: "daysBetween(TaxEventDate, Date)" }` and
`{ kind: compare, field: issueDelayDays, op: le, value: 5, severity: warn, message: "..." }`. Only
the neutral expressions are evaluated for the checks; a `calculatedAction*` runs on the write
itself, so a check on an action-calculated field still reads the value it was sent.
**Translating check messages (#7611).** A check message is translated exactly like a label: write
it ONCE, in the module's default language, and it reaches every reader in their own. The generator
writes each message into the module's en-US catalog (`i18n/en-US/<model>.model.json`) under
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ package gen.${javaGenFolderName}.data.${javaPerspectiveName};
#if($property.isCalculatedProperty && ($property.calculatedActionOnCreate || $property.calculatedActionOnUpdate))
#set($haveCalculatedPropertyAction = "true")
#end
## The expression-calculated fields the checks' candidate row computes (#7544) - an action wins over an
## expression on the same field, exactly as in save() / update(), and an action is never run there.
#if($property.isCalculatedProperty && $property.calculatedPropertyExpressionCreate && !$property.calculatedActionOnCreate)
#set($haveCalculatedExpressionOnCreate = "true")
#end
#if($property.isCalculatedProperty && $property.calculatedPropertyExpressionUpdate && !$property.calculatedActionOnUpdate)
#set($haveCalculatedExpressionOnUpdate = "true")
#end
#if($property.widgetType == "MULTISELECT")
#set($haveSubsets = "true")
#end
Expand Down Expand Up @@ -1283,6 +1291,80 @@ public class ${name}Repository extends JavaRepository<${name}Entity>#if($hasTarg
#end
}
#end

/**
* The record this create would store, as far as its calculated fields go - the row the controller's
* checks read before the write (#7544). The calculated expressions are assigned inside save(), so a
* check reading the submitted record saw whatever the caller sent for a calculated field (usually
* nothing, or the value of a previous save) and a soft or hard check on one never fired for a REST
* write. The defaults the create applies come first, as in save(), since an expression may read
* them. Only the neutral expressions are evaluated: a calculated ACTION may mint or mutate state,
* and runs on the write path alone. The submitted record itself is not touched - what is persisted
* is still computed by save().
*
* @param submitted the record about to be created
* @return a copy carrying the computed values, or the record itself when nothing is calculated
*/
public ${name}Entity calculatedForCreate(${name}Entity submitted) {
#if($haveCalculatedExpressionOnCreate)
${name}Entity entity = copyOf(submitted);
#applyDefaults()
#foreach ($property in $properties)
#if($property.isCalculatedProperty && $property.calculatedPropertyExpressionCreate && !$property.calculatedActionOnCreate)
#calcAssign($property $property.calculatedPropertyExpressionCreate)
#end
#end
return entity;
#else
return submitted;
#end
}

/**
* The record this update would store, as far as its calculated fields go - the row the controller's
* checks read before the write (#7544). Built as update() builds it: the system-owned fields taken
* from the stored row, the document totals recomputed, then the update-time expressions - so an
* expression such as Balance = Total - Paid reads the stored Paid, never the payload's. Only the
* neutral expressions are evaluated (an action runs on the write path alone), and the submitted
* record itself is not touched.
*
* @param submitted the record about to be updated, carrying its id
* @return a copy carrying the computed values, or the record itself when nothing is calculated
*/
public ${name}Entity calculatedForUpdate(${name}Entity submitted) {
#if($haveCalculatedExpressionOnUpdate)
${name}Entity entity = copyOf(submitted);
#if($preservedOnUpdate.size() > 0)
${name}Entity stored = entity.${pkPropertyName} == null ? null : findById(entity.${pkPropertyName});
if (stored != null) {
#foreach($property in $preservedOnUpdate)
entity.${property.name} = stored.${property.name};
#end
}
#end
#if($documentMaster)
recalculate(entity);
#end
#foreach ($property in $properties)
#if($property.isCalculatedProperty && $property.calculatedPropertyExpressionUpdate && !$property.calculatedActionOnUpdate)
#calcAssign($property $property.calculatedPropertyExpressionUpdate)
#end
#end
return entity;
#else
return submitted;
#end
}
#if($haveCalculatedExpressionOnCreate || $haveCalculatedExpressionOnUpdate)

private static ${name}Entity copyOf(${name}Entity source) {
${name}Entity copy = new ${name}Entity();
#foreach ($property in $properties)
copy.${property.name} = source.${property.name};
#end
return copy;
}
#end
#if($warningChecks && $warningChecks.size() > 0)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,14 +256,17 @@ public class ${name}Controller {
#if($workflowStatusProperty)
requireWorkflowStatusOnCreate(entity);
#end
validate(entity);
// The checks read the record as the write will store it - its calculated fields computed, not
// taken from the payload (#7544). What is persisted is still computed by the repository.
${name}Entity checked = repository.calculatedForCreate(entity);
validate(checked);
#if($hasReferenceValidations)
validateReferences(entity);
validateReferences(checked);
#end
#if($warningChecks && $warningChecks.size() > 0)
// The soft checks (severity: warn, #7466): asked of the person saving - a 428 listing them
// until the request confirms their codes - and only then persisted.
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(entity));
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(checked));
#end
#if($systemOwnedOnCreate.size() > 0)
// After every check above, so what a create refuses is unchanged; before the save, so the
Expand Down Expand Up @@ -362,14 +365,17 @@ public class ${name}Controller {
${name}Entity existing = repository.findOne(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "${name} not found"));
mergeWritable(existing, entity);
validate(existing);
// The checks read the record as the write will store it - its calculated fields computed, not
// taken from the payload (#7544). What is persisted is still computed by the repository.
${name}Entity checked = repository.calculatedForUpdate(existing);
validate(checked);
#if($hasReferenceValidations)
validateReferences(existing);
validateReferences(checked);
#end
#if($warningChecks && $warningChecks.size() > 0)
// The soft checks (severity: warn, #7466): asked of the person saving - a 428 listing them
// until the request confirms their codes - and only then persisted.
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(existing));
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(checked));
#end
#if($hasDuplicateKeys)
try {
Expand All @@ -382,14 +388,17 @@ public class ${name}Controller {
#end
#else
entity.#foreach($property in $properties)#if($property.dataPrimaryKey)${property.name}#end#end = id;
validate(entity);
// The checks read the record as the write will store it - its calculated fields computed, not
// taken from the payload (#7544). What is persisted is still computed by the repository.
${name}Entity checked = repository.calculatedForUpdate(entity);
validate(checked);
#if($hasReferenceValidations)
validateReferences(entity);
validateReferences(checked);
#end
#if($warningChecks && $warningChecks.size() > 0)
// The soft checks (severity: warn, #7466): asked of the person saving - a 428 listing them
// until the request confirms their codes - and only then persisted.
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(entity));
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(checked));
#end
#if($hasDuplicateKeys)
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,14 +253,17 @@ public class ${name}MyController {
// sensitive / role-scoped fields are cleared, so validating earlier would judge a payload the
// server has not finished deciding. Before the save, so a refused create never reaches the
// insert - and never allocates a document number.
validate(entity);
// The checks read the record as the write will store it - its calculated fields computed, not
// taken from the payload (#7544). What is persisted is still computed by the repository.
${name}Entity checked = repository.calculatedForCreate(entity);
validate(checked);
#if($hasReferenceValidations)
validateReferences(entity);
validateReferences(checked);
#end
#if($warningChecks && $warningChecks.size() > 0)
// The soft checks (severity: warn, #7466): asked of the person saving - a 428 listing them
// until the request confirms their codes - and only then persisted.
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(entity));
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(checked));
#end
#if($systemOwnedOnCreate.size() > 0)
// After every check above, so what a create refuses is unchanged; before the save, so the
Expand Down Expand Up @@ -314,14 +317,17 @@ public class ${name}MyController {
}
#end
#end
validate(entity);
// The checks read the record as the write will store it - its calculated fields computed, not
// taken from the payload (#7544). What is persisted is still computed by the repository.
${name}Entity checked = repository.calculatedForUpdate(entity);
validate(checked);
#if($hasReferenceValidations)
validateReferences(entity);
validateReferences(checked);
#end
#if($warningChecks && $warningChecks.size() > 0)
// The soft checks (severity: warn, #7466): asked of the person saving - a 428 listing them
// until the request confirms their codes - and only then persisted.
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(entity));
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(checked));
#end
#if($hasDuplicateKeys)
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,17 @@ public class ${name}PartnerController {
// sensitive / role-scoped fields are cleared, so validating earlier would judge a payload the
// server has not finished deciding. Before the save, so a refused create never reaches the
// insert - and never allocates a document number.
validate(entity);
// The checks read the record as the write will store it - its calculated fields computed, not
// taken from the payload (#7544). What is persisted is still computed by the repository.
${name}Entity checked = repository.calculatedForCreate(entity);
validate(checked);
#if($hasReferenceValidations)
validateReferences(entity);
validateReferences(checked);
#end
#if($warningChecks && $warningChecks.size() > 0)
// The soft checks (severity: warn, #7466): asked of the person saving - a 428 listing them
// until the request confirms their codes - and only then persisted.
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(entity));
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(checked));
#end
#if($systemOwnedOnCreate.size() > 0)
// After every check above, so what a create refuses is unchanged; before the save, so the
Expand Down Expand Up @@ -279,14 +282,17 @@ public class ${name}PartnerController {
}
#end
#end
validate(entity);
// The checks read the record as the write will store it - its calculated fields computed, not
// taken from the payload (#7544). What is persisted is still computed by the repository.
${name}Entity checked = repository.calculatedForUpdate(entity);
validate(checked);
#if($hasReferenceValidations)
validateReferences(entity);
validateReferences(checked);
#end
#if($warningChecks && $warningChecks.size() > 0)
// The soft checks (severity: warn, #7466): asked of the person saving - a 428 listing them
// until the request confirms their codes - and only then persisted.
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(entity));
org.eclipse.dirigible.sdk.http.Warnings.requireConfirmed(repository.warnings(checked));
#end
#if($hasDuplicateKeys)
try {
Expand Down
Loading
Loading