Skip to content

Commit

Permalink
Add new "createCorrelationCases" sync action
Browse files Browse the repository at this point in the history
Instead of (experimental and now legacy) correlationDefinition/cases
setting, correlation cases are from this moment on created by a new
<createCorrelationCases/> action in the (new) synchronization
reactions setting.

The old style remains valid for legacy synchronization setting
(that is separate from its object type definition).
  • Loading branch information
mederly committed May 13, 2022
1 parent 3059d6c commit 9657d1f
Show file tree
Hide file tree
Showing 24 changed files with 568 additions and 457 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ public static ClockworkSettings of(@Nullable SynchronizationReactionsDefaultSett
}
}

public static ClockworkSettings empty() {
return new ClockworkSettings();
}

public Boolean getReconcileAll() {
return reconcileAll;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

package com.evolveum.midpoint.schema.processor;

import static com.evolveum.midpoint.xml.ns._public.common.common_3.SynchronizationSituationType.DISPUTED;

import static java.util.Objects.requireNonNullElse;

import static com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType.ACCOUNT;
Expand Down Expand Up @@ -164,17 +166,51 @@ private static boolean isProcessed(ResourceObjectTypeDefinition typeDef, List<Sy
objectDefinition);
}

/**
* Converts legacy synchronization definition bean ({@link ObjectSynchronizationType}) into a list of parsed
* {@link SynchronizationReactionDefinition} objects.
*
* Especially treats the existence of `correlationDefinition/cases` item. If such an item is present,
* "create correlation cases" action is added to "disputed" reaction (or such reaction is created, if there's none).
*/
private static List<SynchronizationReactionDefinition> getSynchronizationReactions(
ObjectSynchronizationType synchronizationBean) throws ConfigurationException {
@NotNull ObjectSynchronizationType synchronizationBean) throws ConfigurationException {
ClockworkSettings defaultSettings = ClockworkSettings.of(synchronizationBean);
boolean legacyCorrelationCasesEnabled = isLegacyCorrelationCasesSettingOn(synchronizationBean);

List<SynchronizationReactionDefinition> list = new ArrayList<>();
for (SynchronizationReactionType synchronizationReactionType : synchronizationBean.getReaction()) {

boolean createCasesActionAdded = false;
for (SynchronizationReactionType synchronizationReactionBean : synchronizationBean.getReaction()) {
boolean addCreateCasesActionHere =
legacyCorrelationCasesEnabled && synchronizationReactionBean.getSituation() == DISPUTED;
list.add(
SynchronizationReactionDefinition.of(synchronizationReactionType, defaultSettings));
SynchronizationReactionDefinition.of(
synchronizationReactionBean, addCreateCasesActionHere, defaultSettings));
if (addCreateCasesActionHere) {
createCasesActionAdded = true;
}
}

if (legacyCorrelationCasesEnabled && !createCasesActionAdded) {
list.add(SynchronizationReactionDefinition.of(
new SynchronizationReactionType().situation(DISPUTED),
true,
ClockworkSettings.empty()));
}

return list;
}

private static boolean isLegacyCorrelationCasesSettingOn(@NotNull ObjectSynchronizationType synchronizationBean) {
LegacyCorrelationDefinitionType correlationDefinition = synchronizationBean.getCorrelationDefinition();
return correlationDefinition != null && isEnabled(correlationDefinition.getCases());
}

private static boolean isEnabled(CorrelationCasesDefinitionType cases) {
return cases != null && !Boolean.FALSE.equals(cases.isEnabled());
}

private static @NotNull CorrelationDefinitionType getCorrelationDefinitionBean(
@NotNull ObjectSynchronizationType synchronizationBean) {
if (synchronizationBean.getCorrelationDefinition() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,31 @@ public class SynchronizationReactionDefinition implements Comparable<Synchroniza
@NotNull private final List<SynchronizationActionDefinition> actions;

private SynchronizationReactionDefinition(
@NotNull SynchronizationReactionType bean, @NotNull ClockworkSettings syncLevelSettings)
@NotNull SynchronizationReactionType legacyBean,
boolean addCreateCasesAction,
@NotNull ClockworkSettings syncLevelSettings)
throws ConfigurationException {
this.order = null;
this.name = bean.getName();
this.name = legacyBean.getName();
this.situations = Set.of(
MiscUtil.requireNonNull(
bean.getSituation(),
() -> new ConfigurationException("Situation is not defined in " + bean)));
this.channels = bean.getChannel();
this.condition = bean.getCondition();
legacyBean.getSituation(),
() -> new ConfigurationException("Situation is not defined in " + legacyBean)));
this.channels = legacyBean.getChannel();
this.condition = legacyBean.getCondition();
this.legacySynchronizeOff =
Boolean.FALSE.equals(bean.isSynchronize())
|| bean.isSynchronize() == null && bean.getAction().isEmpty();
ClockworkSettings reactionLevelSettings = syncLevelSettings.updateFrom(bean);
this.actions = createActions(bean, reactionLevelSettings);
!addCreateCasesAction &&
(Boolean.FALSE.equals(legacyBean.isSynchronize())
|| legacyBean.isSynchronize() == null && legacyBean.getAction().isEmpty());
ClockworkSettings reactionLevelSettings = syncLevelSettings.updateFrom(legacyBean);
this.actions = createActions(legacyBean, reactionLevelSettings);
// Instead of "<cases>" in the correlation definition we create a special "create correlation cases" action.
// We are sure that no such action is there yet, as it's not possible to formulate such an action using action URIs.
if (addCreateCasesAction) {
actions.add(new SynchronizationActionDefinition.New(
new CreateCorrelationCaseSynchronizationActionType(),
ClockworkSettings.empty()));
}
}

private List<SynchronizationActionDefinition> createActions(
Expand Down Expand Up @@ -104,15 +114,17 @@ private List<AbstractSynchronizationActionType> getAllActionBeans(@NotNull Synch
all.addAll(actions.getInactivateFocus());
all.addAll(actions.getDeleteShadow());
all.addAll(actions.getInactivateShadow());
all.addAll(actions.getCreateCorrelationCase());
// TODO support extensions
return all;
}

public static @NotNull SynchronizationReactionDefinition of(
@NotNull SynchronizationReactionType bean,
@NotNull SynchronizationReactionType legacyBean,
boolean addCreateCasesAction,
@NotNull ClockworkSettings defaultSettings)
throws ConfigurationException {
return new SynchronizationReactionDefinition(bean, defaultSettings);
return new SynchronizationReactionDefinition(legacyBean, addCreateCasesAction, defaultSettings);
}

public static @NotNull SynchronizationReactionDefinition of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9064,7 +9064,7 @@
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="correlationDefinition" type="tns:CorrelationDefinitionType" minOccurs="0">
<xsd:element name="correlationDefinition" type="tns:LegacyCorrelationDefinitionType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Definition of the correlation process.
Expand Down Expand Up @@ -23612,21 +23612,41 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<xsd:element name="cases" type="tns:CorrelationCasesDefinitionType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Whether and how should we create correlation cases.
</xsd:documentation>
<xsd:appinfo>
<a:since>4.5</a:since>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:long"/>
</xsd:complexType>
<xsd:element name="correlationDefinition" type="tns:CorrelationDefinitionType"/>

<xsd:complexType name="LegacyCorrelationDefinitionType">
<xsd:annotation>
<xsd:documentation>
This type exists just to allow (deprecated) "cases" element in legacy synchronization bean.
</xsd:documentation>
<xsd:appinfo>
<a:since>4.6</a:since>
<a:container>true</a:container>
<a:experimental>true</a:experimental>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="tns:CorrelationDefinitionType">
<xsd:sequence>
<xsd:element name="cases" type="tns:CorrelationCasesDefinitionType" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
Whether and how should we create correlation cases.
</xsd:documentation>
<xsd:appinfo>
<a:since>4.5</a:since>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:element name="legacyCorrelationDefinition" type="tns:LegacyCorrelationDefinitionType"/>

<xsd:complexType name="CorrelationCasesDefinitionType">
<xsd:annotation>
<xsd:documentation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@
<xsd:element name="inactivateFocus" type="tns:InactivateFocusSynchronizationActionType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="deleteShadow" type="tns:DeleteShadowSynchronizationActionType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="inactivateShadow" type="tns:InactivateShadowSynchronizationActionType" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="createCorrelationCase" type="tns:CreateCorrelationCaseSynchronizationActionType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>

Expand Down Expand Up @@ -481,6 +482,25 @@
</xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="CreateCorrelationCaseSynchronizationActionType">
<xsd:annotation>
<xsd:documentation>
Creates (or updates) a correlation case for resource object in "disputed"
synchronization state, i.e. one whose owner cannot be reliably determined.

TODO Is it OK that correlation case is updated only when this action is configured?
Shouldn't that be automatic (if the situation is DISPUTED, and a case already exists)?
</xsd:documentation>
<xsd:appinfo>
<a:container>true</a:container>
<a:since>4.6</a:since>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexContent>
<xsd:extension base="tns:AbstractSynchronizationActionType"/>
</xsd:complexContent>
</xsd:complexType>

<xsd:complexType name="SuperResourceDeclarationType">
<xsd:annotation>
<xsd:documentation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,6 @@ public CorrelatorContext(
return configuration;
}

public boolean shouldCreateCases() {
return correlationDefinitionBean != null
&& correlationDefinitionBean.getCases() != null
&& !Boolean.FALSE.equals(correlationDefinitionBean.getCases().isEnabled());
}

/**
* Returns the path to the "source place" in the object being correlated.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,13 @@ public void createOrUpdateCase(
@NotNull FocusType preFocus,
@NotNull Task task,
@NotNull OperationResult result)
throws SchemaException {
throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException {
checkOid(resourceObject);
CaseType aCase = findCorrelationCase(resourceObject, true, result);
if (aCase == null) {
createCase(resourceObject, resource, preFocus, task, result);
XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar();
createCase(resourceObject, resource, preFocus, now, task, result);
recordCaseCreationInShadow(resourceObject, now, result);
} else {
updateCase(aCase, preFocus, result);
}
Expand All @@ -109,6 +111,7 @@ private void createCase(
ShadowType resourceObject,
ResourceType resource,
FocusType preFocus,
XMLGregorianCalendar now,
Task task,
OperationResult result)
throws SchemaException {
Expand All @@ -125,7 +128,7 @@ private void createCase(
.schema(createCaseSchema(resource.getBusiness())))
.state(SchemaConstants.CASE_STATE_CREATED)
.metadata(new MetadataType()
.createTimestamp(clock.currentTimeXMLGregorianCalendar()));
.createTimestamp(now));
try {
repositoryService.addObject(newCase.asPrismObject(), null, result);
} catch (ObjectAlreadyExistsException e) {
Expand Down Expand Up @@ -175,6 +178,18 @@ private ObjectReferenceType createArchetypeRef() {
.type(ArchetypeType.COMPLEX_TYPE);
}

private void recordCaseCreationInShadow(ShadowType shadow, XMLGregorianCalendar now, OperationResult result)
throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException {
repositoryService.modifyObject(
ShadowType.class,
shadow.getOid(),
PrismContext.get().deltaFor(ShadowType.class)
.item(ShadowType.F_CORRELATION, ShadowCorrelationStateType.F_CORRELATION_CASE_OPEN_TIMESTAMP)
.replace(now)
.asItemDeltas(),
result);
}

private void updateCase(
CaseType aCase, FocusType preFocus, OperationResult result)
throws SchemaException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,7 @@ class CorrelationProcessing<F extends FocusType> {
CorrelationResult correlationResult = correlateInRootCorrelator(result);
applyResultToShadow(correlationResult);

if (correlationResult.isUncertain()) {
processUncertainResult(result);
} else if (correlationResult.isError()) {
// Nothing to do here
} else {
if (correlationResult.isDone()) {
processFinalResult(result);
}
result.addArbitraryObjectAsReturn("correlationResult", correlationResult);
Expand Down Expand Up @@ -205,24 +201,6 @@ private Correlator instantiateRootCorrelator(OperationResult result) throws Conf
return beans.correlatorFactoryRegistry.instantiateCorrelator(rootCorrelatorContext, task, result);
}

private void processUncertainResult(OperationResult result) throws SchemaException {
if (rootCorrelatorContext.shouldCreateCases()) {
if (getShadowCorrelationCaseOpenTimestamp() == null) {
syncCtx.addShadowDeltas(
PrismContext.get().deltaFor(ShadowType.class)
.item(ShadowType.F_CORRELATION, ShadowCorrelationStateType.F_CORRELATION_CASE_OPEN_TIMESTAMP)
.replace(XmlTypeConverter.createXMLGregorianCalendar())
.asItemDeltas());
}
beans.correlationCaseManager.createOrUpdateCase(
shadow,
syncCtx.getResource(),
syncCtx.getPreFocus(),
task,
result);
}
}

private void processFinalResult(OperationResult result) throws SchemaException {
beans.correlationCaseManager.closeCaseIfStillOpen(getShadow(), result);
// TODO record case close if needed
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2013 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/
package com.evolveum.midpoint.model.impl.sync.action;

import org.jetbrains.annotations.NotNull;

import com.evolveum.midpoint.model.impl.sync.reactions.ActionDefinitionClass;
import com.evolveum.midpoint.model.impl.sync.reactions.ActionInstantiationContext;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.exception.CommonException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.CreateCorrelationCaseSynchronizationActionType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FocusType;

@ActionDefinitionClass(CreateCorrelationCaseSynchronizationActionType.class)
public class CreateCorrelationCaseAction<F extends FocusType> extends BaseAction<F> {

private static final String OP_HANDLE = CreateCorrelationCaseAction.class.getName() + ".handle";

public CreateCorrelationCaseAction(@NotNull ActionInstantiationContext<F> ctx) {
super(ctx);
}

@Override
public void handle(@NotNull OperationResult parentResult) throws CommonException {
OperationResult result = parentResult.subresult(OP_HANDLE).build();
try {
beans.correlationCaseManager.createOrUpdateCase(
syncCtx.getShadowedResourceObject(),
syncCtx.getResource(),
syncCtx.getPreFocus(),
syncCtx.getTask(),
result);
} catch (Throwable t) {
result.recordFatalError(t);
throw t;
} finally {
result.close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ public void register() {
synchronizationActionFactory.register(InactivateShadowAction.class);
synchronizationActionFactory.register(LinkAction.class);
synchronizationActionFactory.register(UnlinkAction.class);
synchronizationActionFactory.register(CreateCorrelationCaseAction.class);
}
}

0 comments on commit 9657d1f

Please sign in to comment.