in core/metamodel/src/main/java/org/apache/causeway/core/metamodel/services/grid/bootstrap/GridSystemServiceBootstrap.java [238:523]
protected boolean validateAndNormalize(
final Grid grid,
final Class<?> domainClass) {
var bsGrid = (BSGrid) grid;
var objectSpec = specificationLoader.specForTypeElseFail(domainClass);
var oneToOneAssociationById = ObjectMember.mapById(objectSpec.streamProperties(MixedIn.INCLUDED));
var oneToManyAssociationById = ObjectMember.mapById(objectSpec.streamCollections(MixedIn.INCLUDED));
var objectActionById = ObjectMember.mapById(objectSpec.streamRuntimeActions(MixedIn.INCLUDED));
var propertyLayoutDataById = bsGrid.getAllPropertiesById();
var collectionLayoutDataById = bsGrid.getAllCollectionsById();
var actionLayoutDataById = bsGrid.getAllActionsById();
var gridModelIfValid = _GridModel.createFrom(bsGrid);
if(!gridModelIfValid.isPresent()) { // only present if valid
return false;
}
var gridModel = gridModelIfValid.get();
var layoutDataFactory = LayoutDataFactory.of(objectSpec);
// * surplus ... those defined in the grid model but not available with the meta-model
// * missing ... those available with the meta-model but missing in the grid-model
// (missing properties will be added to the first field-set of the specified column)
var surplusAndMissingPropertyIds =
surplusAndMissing(propertyLayoutDataById.keySet(), oneToOneAssociationById.keySet());
var surplusPropertyIds = surplusAndMissingPropertyIds.surplus();
var missingPropertyIds = surplusAndMissingPropertyIds.missing();
for (String surplusPropertyId : surplusPropertyIds) {
propertyLayoutDataById.get(surplusPropertyId).setMetadataError("No such property");
}
// catalog which associations are bound to an existing field-set
// so that (below) we can determine which missing property IDs are not unbound vs
// which should be included in the field-set that they are bound to.
var boundAssociationIdsByFieldSetId = _Maps.<String, Set<String>>newHashMap();
for (var fieldSet : gridModel.fieldSets()) {
var fieldSetId = GroupIdAndName.forFieldSet(fieldSet)
.orElseThrow(()->_Exceptions.illegalArgument("invalid fieldSet detected, "
+ "requires at least an id or a name"))
.id();
Set<String> boundAssociationIds = boundAssociationIdsByFieldSetId.get(fieldSetId);
if(boundAssociationIds == null) {
boundAssociationIds = stream(fieldSet.getProperties())
.map(PropertyLayoutData::getId)
.collect(Collectors.toCollection(_Sets::newLinkedHashSet));
boundAssociationIdsByFieldSetId.put(fieldSetId, boundAssociationIds);
}
}
// 1-to-1-association IDs, that want to contribute to the 'metadata' FieldSet
// but are unbound, because such a FieldSet is not defined by the given layout.
var unboundMetadataContributingIds = _Sets.<String>newHashSet();
// along with any specified by existing metadata
for (final OneToOneAssociation oneToOneAssociation : oneToOneAssociationById.values()) {
var layoutGroupFacet = oneToOneAssociation.getFacet(LayoutGroupFacet.class);
if(layoutGroupFacet == null) {
continue;
}
var id = layoutGroupFacet.getGroupId();
if(gridModel.containsFieldSetId(id)) {
Set<String> boundAssociationIds =
boundAssociationIdsByFieldSetId.computeIfAbsent(id, k -> _Sets.newLinkedHashSet());
boundAssociationIds.add(oneToOneAssociation.getId());
} else if(id.equals(LayoutConstants.FieldSetId.METADATA)) {
unboundMetadataContributingIds.add(oneToOneAssociation.getId());
}
}
if(!missingPropertyIds.isEmpty()) {
var unboundPropertyIds = _Sets.newLinkedHashSet(missingPropertyIds);
for (final String fieldSetId : boundAssociationIdsByFieldSetId.keySet()) {
var boundPropertyIds = boundAssociationIdsByFieldSetId.get(fieldSetId);
unboundPropertyIds.removeAll(boundPropertyIds);
}
for (final String fieldSetId : boundAssociationIdsByFieldSetId.keySet()) {
var fieldSet = gridModel.getFieldSet(fieldSetId);
var associationIds = boundAssociationIdsByFieldSetId.get(fieldSetId);
var associations1To1Ids =
associationIds.stream()
.map(oneToOneAssociationById::get)
.filter(_NullSafe::isPresent)
.sorted(ObjectMember.byMemberOrderSequence(false))
.map(ObjectAssociation::getId)
.collect(Collectors.toList());
addPropertiesTo(
fieldSet,
associations1To1Ids,
layoutDataFactory::createPropertyLayoutData,
propertyLayoutDataById::put);
}
if(!unboundPropertyIds.isEmpty()) {
var fieldSet = gridModel.getFieldSetForUnreferencedPropertiesRef();
if(fieldSet != null) {
unboundPropertyIds.removeAll(unboundMetadataContributingIds);
// add unbound properties respecting configured sequence policy
var sortedUnboundPropertyIds = _UnreferencedSequenceUtil
.sortProperties(config, unboundPropertyIds.stream()
.map(oneToOneAssociationById::get)
.filter(_NullSafe::isPresent));
addPropertiesTo(
fieldSet,
sortedUnboundPropertyIds,
layoutDataFactory::createPropertyLayoutData,
propertyLayoutDataById::put);
}
}
}
// any missing collections will be added as tabs to a new TabGroup in the specified column
var surplusAndMissingCollectionIds =
surplusAndMissing(collectionLayoutDataById.keySet(), oneToManyAssociationById.keySet());
var surplusCollectionIds = surplusAndMissingCollectionIds.surplus();
var missingCollectionIds = surplusAndMissingCollectionIds.missing();
for (String surplusCollectionId : surplusCollectionIds) {
collectionLayoutDataById.get(surplusCollectionId).setMetadataError("No such collection");
}
if(!missingCollectionIds.isEmpty()) {
// add missing collections respecting configured sequence policy
var sortedMissingCollectionIds = _UnreferencedSequenceUtil
.sortCollections(config, missingCollectionIds.stream()
.map(oneToManyAssociationById::get)
.filter(_NullSafe::isPresent));
final BSTabGroup bsTabGroup = gridModel.getTabGroupForUnreferencedCollectionsRef();
if(bsTabGroup != null) {
addCollectionsTo(
bsTabGroup,
sortedMissingCollectionIds,
objectSpec,
layoutDataFactory::createCollectionLayoutData);
} else {
final BSCol bsCol = gridModel.getColForUnreferencedCollectionsRef();
if(bsCol != null) {
addCollectionsTo(
bsCol,
sortedMissingCollectionIds,
layoutDataFactory::createCollectionLayoutData,
collectionLayoutDataById::put);
}
}
}
// any missing actions will be added as actions in the specified column
var surplusAndMissingActionIds =
surplusAndMissing(actionLayoutDataById.keySet(), objectActionById.keySet());
var surplusActionIds = surplusAndMissingActionIds.surplus();
var possiblyMissingActionIds = surplusAndMissingActionIds.missing();
final List<String> associatedActionIds = _Lists.newArrayList();
final List<ObjectAction> sortedPossiblyMissingActions =
_Lists.map(possiblyMissingActionIds, objectActionById::get);
sortedPossiblyMissingActions.sort(ObjectMember.byMemberOrderSequence(false));
final List<String> sortedPossiblyMissingActionIds =
_Lists.map(sortedPossiblyMissingActions, ObjectMember::getId);
for (final String actionId : sortedPossiblyMissingActionIds) {
var objectAction = objectActionById.get(actionId);
var layoutGroupFacet = objectAction.getFacet(LayoutGroupFacet.class);
if(layoutGroupFacet == null) {
continue;
}
final String layoutGroupName = layoutGroupFacet.getGroupId();
if (layoutGroupName == null) {
continue;
}
if (oneToOneAssociationById.containsKey(layoutGroupName)) {
associatedActionIds.add(actionId);
if(layoutGroupFacet.isExplicitBinding()) {
final PropertyLayoutData propertyLayoutData = propertyLayoutDataById.get(layoutGroupName);
if(propertyLayoutData == null) {
log.warn(String.format("Could not find propertyLayoutData for layoutGroupName of '%s'", layoutGroupName));
continue;
}
var actionLayoutData = new ActionLayoutData(actionId);
var actionPositionFacet = objectAction.getFacet(ActionPositionFacet.class);
final ActionLayoutDataOwner owner;
final ActionLayout.Position position;
if(actionPositionFacet != null) {
position = actionPositionFacet.position();
owner = position == ActionLayout.Position.PANEL
|| position == ActionLayout.Position.PANEL_DROPDOWN
? propertyLayoutData.getOwner()
: propertyLayoutData;
} else {
position = ActionLayout.Position.BELOW;
owner = propertyLayoutData;
}
actionLayoutData.setPosition(position);
addActionTo(owner, actionLayoutData);
}
continue;
}
if (oneToManyAssociationById.containsKey(layoutGroupName)) {
associatedActionIds.add(actionId);
if(layoutGroupFacet.isExplicitBinding()) {
var collectionLayoutData = collectionLayoutDataById.get(layoutGroupName);
if(collectionLayoutData==null) {
log.warn("failed to lookup CollectionLayoutData by layoutGroupName '{}'", layoutGroupName);
} else {
var actionLayoutData = new ActionLayoutData(actionId);
addActionTo(collectionLayoutData, actionLayoutData);
}
}
continue;
}
// if the @ActionLayout for the action references a field set (that has bound
// associations), then don't mark it as missing, but instead explicitly add it to the
// list of actions of that field-set.
final Set<String> boundAssociationIds = boundAssociationIdsByFieldSetId.get(layoutGroupName);
if(boundAssociationIds != null && !boundAssociationIds.isEmpty()) {
associatedActionIds.add(actionId);
final ActionLayoutData actionLayoutData = new ActionLayoutData(actionId);
// since the action is to be associated with a fieldSet, the only available positions are PANEL and PANEL_DROPDOWN.
// if the action already has a preference for PANEL, then preserve it, otherwise default to PANEL_DROPDOWN
var actionPositionFacet = objectAction.getFacet(ActionPositionFacet.class);
if(actionPositionFacet != null && actionPositionFacet.position() == ActionLayout.Position.PANEL) {
actionLayoutData.setPosition(ActionLayout.Position.PANEL);
} else {
actionLayoutData.setPosition(ActionLayout.Position.PANEL_DROPDOWN);
}
final FieldSet fieldSet = gridModel.getFieldSet(layoutGroupName);
addActionTo(fieldSet, actionLayoutData);
}
}
// ... the missing actions are those in the second tuple, excluding those associated
// (via @Action#associateWith) to a property or collection. (XXX comment might be outdated)
final List<String> missingActionIds = _Lists.newArrayList(sortedPossiblyMissingActionIds);
missingActionIds.removeAll(associatedActionIds);
for (String surplusActionId : surplusActionIds) {
actionLayoutDataById.get(surplusActionId).setMetadataError("No such action");
}
if(!missingActionIds.isEmpty()) {
final BSCol bsCol = gridModel.getColForUnreferencedActionsRef();
if(bsCol != null) {
addActionsTo(
bsCol,
missingActionIds,
layoutDataFactory::createActionLayoutData,
actionLayoutDataById::put);
} else {
final FieldSet fieldSet = gridModel.getFieldSetForUnreferencedActionsRef();
if(fieldSet != null) {
addActionsTo(
fieldSet,
missingActionIds,
layoutDataFactory::createActionLayoutData,
actionLayoutDataById::put);
}
}
}
return true;
}