Skip to content

Commit

Permalink
Fix archetype handling (mainly for linked objects)
Browse files Browse the repository at this point in the history
The code for determination of focus archetype in findLinkedSource,
findLinkedTarget and related methods was too simplistic. It failed
(in different ways) in situations when focus archetype was changed.

Changes in this commit:

1. Code for links resolution has been fixed. Although still not
perfect, it now looks for link definitions in new, current, and
old version of the focus; to cover definitions in both old and new
archetypes. This will work unless there are conflicting link
definitions in old and new archetypes.

2. The algorithm for archetype determination (in ArchetypeDeterminer)
has been simplified by ignoring values of archetypeRef. We kept
considering these values for a long time, mainly because originally
that was the primary mean of defining the archetype. But currently it
causes more troubles than benefits. So, from now on we look only at
archetype assignments in this class. (There are still some places
that look on archetypeRef, but they should NOT be used during clockwork,
when there may be inconsistencies between archetypeRef and assignments.)

Related to this, ObjectTypeUtil.hasArchetype was renamed to
ObjectTypeUtil.hasArchetypeRef, to emphasize the fact it uses
archetypeRef values to determine the result.

3. Determination of "source-linked" objects failed when the focus
OID was null. This is now fixed.

This should fix MID-8060 and MID-8059.
  • Loading branch information
mederly committed Sep 22, 2022
1 parent 06e83dc commit 158a0dc
Show file tree
Hide file tree
Showing 45 changed files with 347 additions and 231 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@ public String getCssClass() {
protected Map<DisplayType, Integer> getIconDisplayType(IModel<SelectableBean<CaseType>> rowModel) {
Map<DisplayType, Integer> map = new HashMap<>();
CaseType caseType = rowModel.getObject().getValue();
if (ObjectTypeUtil.hasArchetype(caseType, SystemObjectsType.ARCHETYPE_OPERATION_REQUEST.value())) {
if (ObjectTypeUtil.hasArchetypeRef(caseType, SystemObjectsType.ARCHETYPE_OPERATION_REQUEST.value())) {
ObjectQuery queryFilter = pageBase.getPrismContext().queryFor(CaseType.class)
.item(CaseType.F_PARENT_REF)
.ref(caseType.getOid())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ public boolean isHeaderMenuItem() {
private boolean isTrace(IModel<?> rowModel) {
//noinspection unchecked
SelectableBean<ReportDataType> row = (SelectableBean<ReportDataType>) rowModel.getObject();
return ObjectTypeUtil.hasArchetype(row.getValue(), SystemObjectsType.ARCHETYPE_TRACE.value());
return ObjectTypeUtil.hasArchetypeRef(row.getValue(), SystemObjectsType.ARCHETYPE_TRACE.value());
}

private IModel<String> createDeleteConfirmString() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ protected void initialize(TaskType taskType) {
};
tagLiveSyncToken.add(new VisibleBehaviour(() -> {
TaskType task = getModelObject();
return task != null && ObjectTypeUtil.hasArchetype(task, SystemObjectsType.ARCHETYPE_LIVE_SYNC_TASK.value());
return task != null && ObjectTypeUtil.hasArchetypeRef(task, SystemObjectsType.ARCHETYPE_LIVE_SYNC_TASK.value());
}));
summaryTagList.add(tagLiveSyncToken);
return summaryTagList;
Expand Down Expand Up @@ -288,7 +288,7 @@ private String getLiveSyncTokenIcon() {
}

private String getLiveSyncToken(TaskType taskType) {
if (!ObjectTypeUtil.hasArchetype(taskType, SystemObjectsType.ARCHETYPE_LIVE_SYNC_TASK.value())) {
if (!ObjectTypeUtil.hasArchetypeRef(taskType, SystemObjectsType.ARCHETYPE_LIVE_SYNC_TASK.value())) {
return null;
}
Object token = taskInformationModel.getObject().getLiveSyncToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -929,11 +929,13 @@ public static List<ObjectReferenceType> keepDistinctReferences(Collection<Object
return rv;
}

public static <AH extends AssignmentHolderType> boolean hasArchetype(PrismObject<AH> object, String oid) {
return hasArchetype(object.asObjectable(), oid);
// BEWARE: Checks archetypeRef. This may be a problem during clockwork processing.
public static <AH extends AssignmentHolderType> boolean hasArchetypeRef(PrismObject<AH> object, String oid) {
return hasArchetypeRef(object.asObjectable(), oid);
}

public static <AH extends AssignmentHolderType> boolean hasArchetype(AH objectable, String oid) {
// BEWARE: Checks archetypeRef. This may be a problem during clockwork processing.
public static <AH extends AssignmentHolderType> boolean hasArchetypeRef(AH objectable, String oid) {
for (ObjectReferenceType orgRef : objectable.getArchetypeRef()) {
if (oid.equals(orgRef.getOid())) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ public static String getRequesterComment(CaseType aCase) {
}

public static boolean isCorrelationCase(@Nullable CaseType aCase) {
return aCase != null && ObjectTypeUtil.hasArchetype(aCase, SystemObjectsType.ARCHETYPE_CORRELATION_CASE.value());
return aCase != null && ObjectTypeUtil.hasArchetypeRef(aCase, SystemObjectsType.ARCHETYPE_CORRELATION_CASE.value());
}

public static boolean isManualProvisioningCase(@Nullable CaseType aCase) {
return aCase != null && ObjectTypeUtil.hasArchetype(aCase, SystemObjectsType.ARCHETYPE_MANUAL_CASE.value());
return aCase != null && ObjectTypeUtil.hasArchetypeRef(aCase, SystemObjectsType.ARCHETYPE_MANUAL_CASE.value());
}

public static boolean isApprovalCase(@Nullable CaseType aCase) {
return aCase != null && ObjectTypeUtil.hasArchetype(aCase, SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value());
return aCase != null && ObjectTypeUtil.hasArchetypeRef(aCase, SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value());
}

public static List<ObjectReferenceType> getAllCurrentAssignees(CaseType aCase) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ public WorkItemId createWorkItemId(CaseWorkItemType workItem) {
}

public boolean isApprovalCase() {
return ObjectTypeUtil.hasArchetype(currentCase, SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value());
return ObjectTypeUtil.hasArchetypeRef(currentCase, SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ private void cancelNonApprovalCase(CaseType aCase, OperationResult result)
}

private boolean isApprovalCase(CaseType aCase) {
return aCase.getArchetypeRef().stream().anyMatch(ref -> SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value().equals(ref.getOid()));
return aCase.getArchetypeRef().stream()
.anyMatch(ref -> SystemObjectsType.ARCHETYPE_APPROVAL_CASE.value().equals(ref.getOid()));
}

private TreeNode<CaseType> getCaseTree(String caseOid, OperationResult result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1346,8 +1346,8 @@ default <O extends ObjectType> boolean hasArchetype(O object, String archetypeOi
/**
* Returns a list of archetype OIDs for given object.
*
* Currently, those OIDs are taken from archetype assignments and `archetypeRef` values.
* (Note that under normal conditions, these should be in sync!)
* Currently, those OIDs are taken from archetype assignments.
* ArchetypeRef values are ignored.
*/
@NotNull List<String> getArchetypeOids(ObjectType object);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
/**
* Manages link definitions.
*
* Current implementation is very limited as it deals only with locally-defined links in in archetype.
* Current implementation is very limited as it deals only with locally-defined links in an archetype.
* Future extensions:
* - consider links defined in object policy configuration (in system configuration)
* - consider links globally e.g. target link A->B defined in archetype A is visible as source link in archetype B
Expand All @@ -39,56 +39,70 @@ public class LinkManager {

@Autowired private ArchetypeManager archetypeManager;

@NotNull
public <O extends ObjectType> LinkTypeDefinitionType getSourceLinkTypeDefinitionRequired(String linkTypeName,
PrismObject<O> object, OperationResult result) throws SchemaException, ConfigurationException {
LinkTypeDefinitionType definition = getSourceLinkTypeDefinition(linkTypeName, object, result);
if (definition != null) {
return definition;
} else {
throw new IllegalStateException("No source link '" + linkTypeName + "' present in " + object);
}
public @NotNull LinkTypeDefinitionType getSourceLinkTypeDefinitionRequired(
String linkTypeName,
List<PrismObject<? extends ObjectType>> objectVariants,
OperationResult result) throws SchemaException, ConfigurationException {
return MiscUtil.requireNonNull(
getSourceLinkTypeDefinition(linkTypeName, objectVariants, result),
() -> new ConfigurationException("No source link '" + linkTypeName + "' present in " +
MiscUtil.getFirstNonNullFromList(objectVariants)));
}

public <O extends ObjectType> LinkTypeDefinitionType getSourceLinkTypeDefinition(String linkTypeName,
PrismObject<O> object, OperationResult result) throws SchemaException, ConfigurationException {
ArchetypePolicyType archetypePolicyType = determineArchetypePolicy(object, result);
if (archetypePolicyType == null || archetypePolicyType.getLinks() == null) {
return null;
} else {
return getLinkDefinition(linkTypeName, archetypePolicyType.getLinks().getSourceLink());
public LinkTypeDefinitionType getSourceLinkTypeDefinition(
String linkTypeName,
List<PrismObject<? extends ObjectType>> objectVariants,
OperationResult result) throws SchemaException, ConfigurationException {
for (PrismObject<? extends ObjectType> objectVariant : objectVariants) {
if (objectVariant != null) {
ArchetypePolicyType archetypePolicy = archetypeManager.determineArchetypePolicy(objectVariant, result);
LinkTypeDefinitionsType links = archetypePolicy != null ? archetypePolicy.getLinks() : null;
if (links != null) {
LinkTypeDefinitionType definition = getLinkDefinition(linkTypeName, links.getSourceLink());
if (definition != null) {
return definition;
}
}
}
}
return null;
}

@NotNull
public <O extends ObjectType> LinkTypeDefinitionType getTargetLinkTypeDefinitionRequired(String linkTypeName,
PrismObject<O> object, OperationResult result) throws SchemaException, ConfigurationException {
LinkTypeDefinitionType definition = getTargetLinkTypeDefinition(linkTypeName, object, result);
if (definition != null) {
return definition;
} else {
throw new IllegalStateException("No target link '" + linkTypeName + "' present in " + object);
}
public @NotNull LinkTypeDefinitionType getTargetLinkTypeDefinitionRequired(
String linkTypeName,
List<PrismObject<? extends ObjectType>> objectVariants,
OperationResult result) throws SchemaException, ConfigurationException {
return MiscUtil.requireNonNull(
getTargetLinkTypeDefinition(linkTypeName, objectVariants, result),
() -> new ConfigurationException("No target link '" + linkTypeName + "' present in " +
MiscUtil.getFirstNonNullFromList(objectVariants)));
}

public <O extends ObjectType> LinkTypeDefinitionType getTargetLinkTypeDefinition(String linkTypeName,
PrismObject<O> object, OperationResult result) throws SchemaException, ConfigurationException {
ArchetypePolicyType archetypePolicyType = determineArchetypePolicy(object, result);
if (archetypePolicyType == null || archetypePolicyType.getLinks() == null) {
return null;
} else {
return getLinkDefinition(linkTypeName, archetypePolicyType.getLinks().getTargetLink());
public LinkTypeDefinitionType getTargetLinkTypeDefinition(
String linkTypeName,
List<PrismObject<? extends ObjectType>> objectVariants,
OperationResult result) throws SchemaException, ConfigurationException {
for (PrismObject<? extends ObjectType> objectVariant : objectVariants) {
if (objectVariant != null) {
ArchetypePolicyType archetypePolicy = archetypeManager.determineArchetypePolicy(objectVariant, result);
LinkTypeDefinitionsType links = archetypePolicy != null ? archetypePolicy.getLinks() : null;
if (links != null) {
LinkTypeDefinitionType definition = getLinkDefinition(linkTypeName, links.getTargetLink());
if (definition != null) {
return definition;
}
}
}
}
return null;
}

private LinkTypeDefinitionType getLinkDefinition(String linkTypeName, List<LinkTypeDefinitionType> definitions) {
List<LinkTypeDefinitionType> matchingDefinitions = definitions.stream()
.filter(def -> linkTypeName.equals(def.getName()))
.collect(Collectors.toList());
return MiscUtil.extractSingleton(matchingDefinitions, () -> new IllegalStateException("Multiple link definitions named '" + linkTypeName + "'."));
}

private <O extends ObjectType> ArchetypePolicyType determineArchetypePolicy(PrismObject<O> object, OperationResult result) throws SchemaException, ConfigurationException {
return archetypeManager.determineArchetypePolicy(object, result);
return MiscUtil.extractSingleton(
matchingDefinitions,
() -> new IllegalStateException("Multiple link definitions named '" + linkTypeName + "'."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@

package com.evolveum.midpoint.model.common.archetypes;

import static com.evolveum.midpoint.util.MiscUtil.stateCheck;

import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.evolveum.midpoint.prism.impl.binding.AbstractReferencable;
import com.evolveum.midpoint.schema.RelationRegistry;
import com.evolveum.midpoint.util.QNameUtil;
Expand All @@ -17,23 +28,10 @@
import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType;

import com.google.common.collect.Sets;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import static com.evolveum.midpoint.util.MiscUtil.stateCheck;

/**
* Responsible for determining archetypes for an object.
*
* Basically, we concentrate on archetype assignments. But - to be extra safe - we also look at `archetypeRef` values,
* but with caution (in cases where object is being modified). See the code.
* We concentrate on archetype assignments. Starting with 4.6 we ignore `archetypeRef` values.
*/
@Component
class ArchetypeDeterminer {
Expand All @@ -49,63 +47,26 @@ class ArchetypeDeterminer {
Set<String> oids;
if (object instanceof AssignmentHolderType) {
AssignmentHolderType assignmentHolder = (AssignmentHolderType) object;
// To be safe, we look also at archetypeRef values. This may change in the future.
oids = Sets.union(
getArchetypeOidsFromAssignments(assignmentHolder),
determineArchetypeOidsFromArchetypeRef(assignmentHolder));
oids = getArchetypeOidsFromAssignments(assignmentHolder);
} else {
oids = Set.of();
}
LOGGER.trace("Archetype OIDs determined (no-change case): {}", oids);
LOGGER.trace("Archetype OIDs determined: {}", oids);
return oids;
}

/**
* Determines all archetype OIDs in the "dynamic" case where the object is changed during clockwork processing.
*/
<O extends ObjectType> @NotNull Set<String> determineArchetypeOids(O before, O after) {

// First, let us sort out static cases, where there is no change in the object internals.

if (after == null) {
// Object is being deleted. Let us simply take the OIDs from last known version (if there's any).
return determineArchetypeOids(before);
}

if (before == null) {
// Object is being added, and we have no "before" version. Reduces to the static case just as above.
} else {
// Values assigned "at the end" (i.e. in `after` version). This is the authoritative information.
// Starting with 4.6, we simply ignore archetypeRef values, so it is sufficient to look here.
return determineArchetypeOids(after);
}

// Here we know we have (some) change. Let's sort things out.

if (!(after instanceof AssignmentHolderType)) {
assert !(before instanceof AssignmentHolderType); // We know the clockwork does not change the type of objects.
return Set.of();
}

return determineArchetypeOids(
(AssignmentHolderType) before,
(AssignmentHolderType) after);
}

private @NotNull Set<String> determineArchetypeOids(
@NotNull AssignmentHolderType before, @NotNull AssignmentHolderType after) {

// Values assigned "at the end" (i.e. in `after` version). This is usually the authoritative information.
Set<String> assignedOidsAfter = getArchetypeOidsFromAssignments(after);

// We look at archetypeRef because there may be rare (theoretical?) cases when we have archetypeRefs
// without corresponding assignments. But we have to be careful to select only relevant OIDs!
Set<String> relevantOidsFromRef = getRelevantArchetypeOidsFromArchetypeRef(before, after, assignedOidsAfter);

// Finally, we take a union of these sets. Note that they usually overlap.
var oids = Sets.union(
assignedOidsAfter,
relevantOidsFromRef);

LOGGER.trace("Archetype OIDs determined (dynamic case): {}", oids);
return oids;
}

private @NotNull Set<String> getArchetypeOidsFromAssignments(AssignmentHolderType assignmentHolder) {
Expand All @@ -121,39 +82,4 @@ class ArchetypeDeterminer {
LOGGER.trace("Assigned archetype OIDs: {}", oids);
return oids;
}

/**
* Selects relevant archetype OIDs from `archetypeRef`. We must take care here because this information may be out of date
* in situations where we try to determine archetypes _before_ the assignment evaluator updates `archetypeRef` values.
* (Even the values in `after` version of the object are not up-to-date in that case.)
*
* The best we can do is to eliminate any `archetypeRef` OIDs that were assigned "before" but are not assigned "after":
* meaning they are unassigned by some (primary/secondary) delta.
*/
private Set<String> getRelevantArchetypeOidsFromArchetypeRef(
AssignmentHolderType before, AssignmentHolderType after, Set<String> assignedOidsAfter) {

Set<String> assignedOidsBefore = getArchetypeOidsFromAssignments(before);

// These are OIDs that were assigned at the beginning, but are no longer assigned. These are forbidden to use.
Set<String> unassignedOids = Sets.difference(assignedOidsBefore, assignedOidsAfter);

// These are the relevant OIDs: they are in (presumed ~ "after") archetypeRef and were not unassigned.
var relevant = Sets.difference(
determineArchetypeOidsFromArchetypeRef(after),
unassignedOids);

LOGGER.trace("Relevant archetype OIDs from archetypeRef: {}", relevant);
return relevant;
}

private @NotNull Set<String> determineArchetypeOidsFromArchetypeRef(AssignmentHolderType assignmentHolder) {
var oids = assignmentHolder.getArchetypeRef().stream()
.filter(ref -> relationRegistry.isMember(ref.getRelation())) // should be always the case
.map(AbstractReferencable::getOid)
.filter(Objects::nonNull) // should be always the case
.collect(Collectors.toSet());
LOGGER.trace("'Effective' archetype OIDs (from archetypeRef): {}", oids);
return oids;
}
}

0 comments on commit 158a0dc

Please sign in to comment.