-
Notifications
You must be signed in to change notification settings - Fork 7
Post Actions: parent master entity binding
Property / Entity actions on Entity Master sometimes require automatic saving of parent entity. For example, consider CertificationRevalidation
entity that can either be just SAVEd or APPROVEd (i.e. SAVEd and approved with approveDate
indication). When approval is performed, the user usually changes some properties and, without waiting for validation, immediately taps APPROVE button. This means that CertificationRevalidation
is still in construction phase and may even become erroneous during ApproveCertificationRevalidationAction
execution.
So, the entity being approved can have its own changes, initiated by the user, and changes from actual approval (e.g. approveDate
populated). Both types of changes should be reflected on successful / erroneous action execution.
Since appearance of Title actions on Entity Master we have developed quite sofisticated refreshing mechanism of parent Entity Masters / Centres down to the very root of hierarchy. In case of successful approval the user would see This property has been recently changed.
warning on approveDate
property. This warning, however, does not add any value for the user as it is them who made the change. Also, CertificationRevalidation
may even become not editable after approval and this can trigger error Certification Revalidation is approved - no changes allowed.
(see #1777 for more).
To avoid these potential warnings / errors we can use BindSavedPropertyPostActionSuccess/Error
post actions with the idea to capture master entity being saved in a producer of the action and then use that master entity for actual alteration and saving. So, how can this be implemented?
At first, add certificationRevalidationToBind
property to ApproveCertificationRevalidationAction
to hold the entity being saved.
@IsProperty
@SkipEntityExistsValidation // only add this annotation iff newly created entity can be saved and immediately approved
private CertificationRevalidation certificationRevalidationToBind;
Even though we added certificationRevalidationToBind
property, this property will not be used in ApproveCertificationRevalidationAction
master (it is actually no-UI master). That's why no change to ApproveCertificationRevalidationActionCo.FETCH_PROVIDER
is needed.
Then we need to populate certificationRevalidationToBind
property. Definitely, we shouldn't execute action against invalid entity. Please, check its validity as early as possible. Also, please ask yourself whether user initiated changes should be saved as a whole with actual approval process. If not, please check the dirtiness of the entity and throw an error.
@Override
@Authorise(ApproveCertificationRevalidationAction_CanExecute_Token.class)
protected ApproveCertificationRevalidationAction provideDefaultValues(final ApproveCertificationRevalidationAction entity) {
if (masterEntityInstanceOf(CertificationRevalidation.class)) {
// masterEntity() must be an instrumented instance from parent Entity Master
final CertificationRevalidation masterEntity = masterEntity(CertificationRevalidation.class);
// make sure that masterEntity is valid before continuing
masterEntity.isValid().ifFailure(Result::throwRuntime);
// [optional] if master entity is dirty, we may need to encourage the user to save or cancel their changes
if (masterEntity.isDirty()) {
throw Result.failure("Please save or cancel changes before using this action.");
}
// set exact instance taken from parent master;
// this instance will be bound to parent master in case of action error on save
entity.setCertificationRevalidationToBind(masterEntity);
}
return super.provideDefaultValues(entity);
}
The next step would be to do some manipulations with certificationRevalidationToBind
property value (e.g. take care of approveDate
) and actually save it.
@Override
@SessionRequired
@Authorise(ApproveCertificationRevalidationAction_CanExecute_Token.class)
public ApproveCertificationRevalidationAction save(final ApproveCertificationRevalidationAction action) {
final CertificationRevalidation masterEntity = action.getCertificationRevalidation();
// validate as early as possible to avoid unnecessary checks for already invalid instance;
// the same validation is done in producer, however that is UI logic and better to be sure to perform the same in model-driven code
masterEntity.isValid().ifFailure(Result::throwRuntime);
masterEntity.setApproveDate(now().toDate());
// ... other alterations
// actually save master entity
final CertificationRevalidation savedMasterEntity = co$(CertificationRevalidation.class).save(masterEntity);
// it is important to set the same (in terms of equals()) entity with enforcement;
// otherwise previous unsaved version will be preserved and it would not be suitable for parent entity master binding
action.getProperty("certificationRevalidationToBind").setValue(savedMasterEntity, true);
return super.save(action);
}
And, finally, need to specify which property will be used to get binding entity for parent master.
/**
* Creates ApproveCertificationRevalidationAction to be used on CertificationRevalidation master.
*/
public static EntityActionConfig createApproveAction() {
return action(ApproveCertificationRevalidationAction.class)
.withContext(context().withMasterEntity().build())
// setting "certificationRevalidationToBind" in companion's `save` is required for successful binding:
.postActionSuccess(new BindSavedPropertyPostActionSuccess("certificationRevalidationToBind"))
// setting "certificationRevalidationToBind" in producer's `provideDefaultValues` is required for erroneous binding:
.postActionError(new BindSavedPropertyPostActionError("certificationRevalidationToBind"))
.shortDesc("Approve")
.longDesc(format("Approve %s", CertificationRevalidation.ENTITY_TITLE))
.build();
}
What about the case of action, that has its UI and shows master entity as one of its properties? Consider example where RenumberVehicleAction
should show parent Vehicle
in RenumberVehicleAction
master. In this case we want to have vehicle
and vehicleToBind
properties separate. vehicle
property would need to be specified in RenumberVehicleActionCo.FETCH_PROVIDER
as usually and added to master configuration. vehicleToBind
property flow is to be exactly as described above (in ApproveCertificationRevalidationAction
case).
Please note, that existence of UI master may require refetching of vehicleToBind
property and validation for conflicts. This is because user may open an action and go for a walk / coffee without actually executing it.
final Vehicle vehicleToBindRefetched = co$Vehicle.findByKeyAndFetch(VehicleCo.FETCH_MODEL, action.getVehicleToBind().getNumber());
// ... validate vehicleToBindRefetched / action.getVehicleToBind() for conflicts if necessary
vehicleToBindRefetched.setNumber(action.getNewNumber());
final Vehicle renumberedVehicle = co$Vehicle.save(vehicleToBindRefetched);
// it is important to set the same (in terms of equals()) entity with enforcement;
// otherwise previous unsaved version will be preserved and it would not be suitable for parent entity master binding
action.getProperty("vehicleToBind").setValue(renumberedVehicle, true);
Some another interesting example of parent master entity binding is where action is defined as EntityNewAction
for some persistent entity. For example, we add PositionAllocation
as +
action on Vehicle.companyPosition
editor. Creation of PositionAllocation
may affect Vehicle
itself (e.g. its usage
property). In this case BindSavedPropertyPostActionSuccess/Error
can not be used, but we can remedy warnings on usage/companyPosition
editors using the following approach (not ideal though):
.addProp(Vehicle_.companyPosition()).asAutocompleter().withAction(mkAddNewPropAction(PositionAllocation.class, format("""
// only consider embedded PositionAllocation master successful save, not EntityNewAction master
if (functionalEntity.type().fullClassName() === '%s') {
// Vehicle.usage may be changed on PositionAllocation save;
// increase client-side Vehicle version to avoid self-conflict checks in case
// if Vehicle.usage was indeed changed and Vehicle version indeed increased
self._currBindingEntity['version'] += 1;
}
""", PositionAllocation.class.getName()))).also()
Per aspera ad astra
- Web UI Design and Web API
- Safe Communication and User Authentication
- Gitworkflow
- JavaScript: Testing with Maven
- Java Application Profiling
-
TG Development Guidelines
- TLS and HAProxy for development
- TG Development Checklist
- Entities and their validation
- Entity Properties
- Entity Type Enhancement
- EQL
- Tooltip How To
- All about Matchers
- All about Fetch Models
- Streaming data
- Synthetic entities
- Activatable entities
- Jasper Reports
- Opening Compound Master from another Compound Master
- Window management test plan
- Multi Time Zone Environment
- GraphQL Web API
- Guice
- Maven
- Full Text Search
- Deployment recipes
- Application Configuration
- JRebel Installation and Integration
- Compile-time mechanisms
- Work in progress