Skip to content

Commit

Permalink
Make ID Match integration more user-centric
Browse files Browse the repository at this point in the history
The correlation is now based on matching midPoint user properties,
instead of shadow attributes as it was before. The result of ID Match
correlation is the owner OID instead of (raw) Reference ID.

Also:

1. Correlation context in cases is simplified. No more correlator-
specific content is there. Options are represented purely as owner
references (with confidence). Shadow is still there, but the correlated
object is primarily represented as focus object.

2. This means that ID Match correlator now uses focus instead
of resource object as a source of data to be sent to the ID Match
service. Note that pre-mappings (i.e. pre-clockwork execution of
real inbound mappings) are only simulated now.

3. ID Match uses a default SOR of "midPoint". SOR IDs are prefixed
by configurable string. The matchgrid ID is now configurable as part
of ID Match service URL.

4. Improved correlation GUI: configurable correlation properties,
ability to distinguish primary and secondary values of these properties,
distinguishing full (green) and partial (yellow) matches.

Work in progress.
  • Loading branch information
mederly committed Jan 27, 2022
1 parent b451d4f commit 88fe014
Show file tree
Hide file tree
Showing 44 changed files with 1,428 additions and 516 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import javax.management.ObjectName;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.model.api.correlator.CorrelationService;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.security.api.MidPointPrincipal;
import com.evolveum.midpoint.security.api.OwnerResolver;
Expand Down Expand Up @@ -292,6 +293,8 @@ public abstract class PageBase extends WebPage implements ModelServiceLocator {

@SpringBean private AdminGuiConfigurationMergeManager adminGuiConfigurationMergeManager;

@SpringBean private CorrelationService correlationService;

private List<Breadcrumb> breadcrumbs;

private boolean initialized = false;
Expand Down Expand Up @@ -509,6 +512,11 @@ public AdminGuiConfigurationMergeManager getAdminGuiConfigurationMergeManager()
return adminGuiConfigurationMergeManager;
}

@Override
public CorrelationService getCorrelationService() {
return correlationService;
}

@NotNull
@Override
public CompiledGuiProfile getCompiledGuiProfile() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.evolveum.midpoint.model.api.ModelInteractionService;
import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.model.api.authentication.CompiledGuiProfile;
import com.evolveum.midpoint.model.api.correlator.CorrelationService;
import com.evolveum.midpoint.model.api.interaction.DashboardService;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.PrismContext;
Expand Down Expand Up @@ -101,4 +102,5 @@ default ObjectResolver getModelObjectResolver() {

AdminGuiConfigurationMergeManager getAdminGuiConfigurationMergeManager();

CorrelationService getCorrelationService();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,76 +9,148 @@

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.xml.ns._public.common.common_3.IdMatchCorrelationContextType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.IdMatchCorrelationPotentialMatchType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowAttributesType;
import org.jetbrains.annotations.Nullable;

import com.evolveum.midpoint.gui.api.page.PageBase;
import com.evolveum.midpoint.model.api.correlator.CorrelatorInstantiationContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.CommonException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

/**
* Represents the whole correlation context: a set of options, including "new owner" one.
*/
public class CorrelationContextDto implements Serializable {

private final List<String> matchHeaders = new ArrayList<>();
private final List<ItemPath> attributes = new ArrayList<>();
private final List<PotentialMatchDto> potentialMatches = new ArrayList<>();
private static final Trace LOGGER = TraceManager.getTrace(CorrelationContextDto.class);

static final String F_OPTION_HEADERS = "optionHeaders";
static final String F_CORRELATION_OPTIONS = "correlationOptions";
static final String F_CORRELATION_PROPERTIES = "correlationProperties";

private static final String HEADER_NEW_OWNER = "New owner";
private static final String HEADER_SUGGESTION = "Suggestion %d";

/**
* Headers for individual options: "New owner", "Suggestion 1", "Suggestion 2", ...
*/
private final List<String> optionHeaders = new ArrayList<>();

/**
* All correlation options. Correspond to columns in the correlation options table.
*
* The first one is the "new owner" option.
*/
private final List<CorrelationOptionDto> correlationOptions = new ArrayList<>();

/**
* Properties that will be presented by the GUI. Obtained either from the correlation configuration
* or determined dynamically.
*
* Correspond to rows in the correlation options table.
*/
private final List<CorrelationPropertyDefinition> correlationProperties = new ArrayList<>();

CorrelationContextDto(CaseType aCase, PageBase pageBase, Task task, OperationResult result) throws CommonException {
load(aCase, pageBase, task, result);
}

private PotentialMatchDto origin;
private void load(CaseType aCase, PageBase pageBase, Task task, OperationResult result) throws CommonException {
resolveObjectsInCorrelationContext(aCase.getCorrelationContext(), pageBase, task, result);
createCorrelationOptions(aCase.getCorrelationContext());
createCorrelationPropertiesDefinitions(aCase, pageBase, task, result);
}

public CorrelationContextDto(IdMatchCorrelationContextType idMatchCorrelationContext) {
List<IdMatchCorrelationPotentialMatchType> idMatches = idMatchCorrelationContext.getPotentialMatch();
loadPotentialMatchesHeader(idMatches);
private void resolveObjectsInCorrelationContext(CorrelationContextType correlationContext, PageBase pageBase, Task task, OperationResult result) {
for (PotentialOwnerType potentialOwner : correlationContext.getPotentialOwners().getPotentialOwner()) {
resolve(potentialOwner.getCandidateOwnerRef(), "candidate " + potentialOwner.getUri(), pageBase, task, result);
}
}

private void loadPotentialMatchesHeader(List<IdMatchCorrelationPotentialMatchType> potentialMatchTypes) {
private void resolve(ObjectReferenceType ref, String desc, PageBase pageBase, Task task, OperationResult result) {
LOGGER.trace("Resolving {}: {}", desc, ref);
if (ref == null || ref.getOid() == null) {
LOGGER.trace("Null ref or no OID");
return;
}
if (ref.getObject() != null) {
LOGGER.trace("Already resolved");
return;
}
ref.asReferenceValue().setObject(
loadObject(ref.getOid(), pageBase, task, result));
}

private PrismObject<?> loadObject(String oid, PageBase pageBase, Task task, OperationResult result) {
try {
return pageBase.getModelService()
.getObject(FocusType.class, oid, null, task, result);
} catch (Exception e) {
result.recordFatalError(e);
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't resolve focus {}", e, oid);
return null;
}
}

int i = 1;
for (IdMatchCorrelationPotentialMatchType potentialMatch : potentialMatchTypes) {
if (SchemaConstants.CORRELATION_NONE_URI.equals(potentialMatch.getUri())) {
origin = new PotentialMatchDto(potentialMatch);
origin.setOrigin(true);
private void createCorrelationOptions(CorrelationContextType context) {
int suggestionNumber = 1;
for (PotentialOwnerType potentialOwner : context.getPotentialOwners().getPotentialOwner()) {
if (SchemaConstants.CORRELATION_NONE_URI.equals(potentialOwner.getUri())) {
optionHeaders.add(0, HEADER_NEW_OWNER);
correlationOptions.add(0,
new CorrelationOptionDto(context.getPreFocusRef()));
} else {
parsePotentialMatch(potentialMatch, i);
i++;
optionHeaders.add(String.format(HEADER_SUGGESTION, suggestionNumber));
correlationOptions.add(
new CorrelationOptionDto(potentialOwner));
suggestionNumber++;
}

}

matchHeaders.add(0, "Origin");
parseAttributeNames(origin);
potentialMatches.add(0, origin);
}

public boolean match(Serializable value, ItemPath path) {
Serializable originValue = origin.getAttributeValue(path);
return Objects.equals(value, originValue);
private void createCorrelationPropertiesDefinitions(CaseType aCase, PageBase pageBase, Task task, OperationResult result)
throws CommonException {
CorrelatorInstantiationContext instantiationContext =
pageBase.getCorrelationService().getInstantiationContext(aCase.asPrismObject(), task, result);
CorrelationPropertiesDefinitionType propertiesBean = instantiationContext.synchronizationBean.getCorrelationProperties();
CorrelationOptionDto newOwnerOption = getNewOwnerOption();
if (propertiesBean != null) {
PrismObject<?> preFocus = newOwnerOption != null ? newOwnerOption.getObject() : null;
CorrelationPropertyDefinition.fillFromConfiguration(correlationProperties, propertiesBean, preFocus);
} else {
if (newOwnerOption == null) {
LOGGER.warn("Couldn't create property definitions from 'new owner' focus object because there's none");
} else {
CorrelationPropertyDefinition.fillFromObject(correlationProperties, newOwnerOption.getObject());
}
}
}

private void parsePotentialMatch(IdMatchCorrelationPotentialMatchType potentialMatch, int iterator) {
PotentialMatchDto potentialMatchDto = new PotentialMatchDto(potentialMatch);
@Nullable CorrelationOptionDto getNewOwnerOption() {
if (correlationOptions.isEmpty()) {
return null;
}
CorrelationOptionDto first = correlationOptions.get(0);
return first.isNewOwner() ? first : null;
}

matchHeaders.add("Suggestion " + iterator);
potentialMatches.add(potentialMatchDto);
public List<CorrelationOptionDto> getCorrelationOptions() {
return correlationOptions;
}

private void parseAttributeNames(PotentialMatchDto origin) {
if (origin == null) {
return;
}
ShadowAttributesType shadowAttributes = origin.getShadowAttributesType();
if (shadowAttributes == null) {
return;
}
Collection<Item<?, ?>> items = shadowAttributes.asPrismContainerValue().getItems();
for (Item item : items) {
attributes.add(item.getElementName());
}
public List<String> getOptionHeaders() {
return optionHeaders;
}

public List<PotentialMatchDto> getPotentialMatches() {
return potentialMatches;
public List<CorrelationPropertyDefinition> getCorrelationProperties() {
return correlationProperties;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
</wicket:container>
</tr>
<tr>
<th>Reference ID</th>
<wicket:container wicket:id="referenceIds">
<td wicket:id="referenceId"></td>
<th>Name</th>
<wicket:container wicket:id="names">
<td wicket:id="name"></td>
</wicket:container>
</tr>
<wicket:container wicket:id="rows">
Expand Down

0 comments on commit 88fe014

Please sign in to comment.