Skip to content

Commit

Permalink
Change ShadowResultHandler to FetchedObjectHandler
Browse files Browse the repository at this point in the history
We no longer return simple PrismObject<ShadowType> instances. Now we
return more complex structures, carrying auxiliary information like
the exact exception thrown.

This is necessary to ensure more precise error handling
in provisioning "get" and "search" operations.
  • Loading branch information
mederly committed Feb 5, 2021
1 parent 5a07942 commit 0f08d64
Show file tree
Hide file tree
Showing 21 changed files with 343 additions and 226 deletions.
Expand Up @@ -16,6 +16,7 @@
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.path.ItemName;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.provisioning.ucf.api.*;
import com.evolveum.midpoint.schema.processor.*;
import com.evolveum.midpoint.schema.processor.ObjectFactory;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
Expand All @@ -31,12 +32,6 @@
import com.evolveum.midpoint.prism.query.ObjectQuery;
import com.evolveum.midpoint.prism.util.PrismUtil;
import com.evolveum.midpoint.provisioning.api.GenericConnectorException;
import com.evolveum.midpoint.provisioning.ucf.api.AttributesToReturn;
import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance;
import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException;
import com.evolveum.midpoint.provisioning.ucf.api.Operation;
import com.evolveum.midpoint.provisioning.ucf.api.PropertyModificationOperation;
import com.evolveum.midpoint.provisioning.ucf.api.ShadowResultHandler;
import com.evolveum.midpoint.provisioning.util.ProvisioningUtil;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
Expand Down Expand Up @@ -235,29 +230,27 @@ private <S extends ShadowType,T> void postProcessEntitlementEntitlementToSubject

SearchHierarchyConstraints searchHierarchyConstraints = determineSearchHierarchyConstraints(entitlementCtx, parentResult);

ShadowResultHandler handler = new ShadowResultHandler() {
@Override
public boolean handle(PrismObject<ShadowType> entitlementShadow) {
PrismContainerValue<ShadowAssociationType> associationCVal = associationContainer.createNewValue();
associationCVal.asContainerable().setName(associationName);
Collection<ResourceAttribute<?>> entitlementIdentifiers = ShadowUtil.getAllIdentifiers(entitlementShadow);
try {
ResourceAttributeContainer identifiersContainer = ObjectFactory.createResourceAttributeContainer(
ShadowAssociationType.F_IDENTIFIERS, entitlementDef.toResourceAttributeContainerDefinition(), prismContext);
associationCVal.add(identifiersContainer);
identifiersContainer.getValue().addAll(Item.cloneCollection(entitlementIdentifiers));

// Remember the full shadow in user data. This is used later as an optimization to create the shadow in repo
identifiersContainer.setUserData(ResourceObjectConverter.FULL_SHADOW_KEY, entitlementShadow);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Processed entitlement-to-subject association for account {} and entitlement {}",
ShadowUtil.getHumanReadableName(resourceObject), ShadowUtil.getHumanReadableName(entitlementShadow));
}
} catch (SchemaException e) {
throw new TunnelException(e);
FetchedObjectHandler handler = ucfObject -> {
PrismObject<ShadowType> entitlementShadow = ucfObject.getResourceObject();
PrismContainerValue<ShadowAssociationType> associationCVal = associationContainer.createNewValue();
associationCVal.asContainerable().setName(associationName);
Collection<ResourceAttribute<?>> entitlementIdentifiers = ShadowUtil.getAllIdentifiers(entitlementShadow);
try {
ResourceAttributeContainer identifiersContainer = ObjectFactory.createResourceAttributeContainer(
ShadowAssociationType.F_IDENTIFIERS, entitlementDef.toResourceAttributeContainerDefinition(), prismContext);
associationCVal.add(identifiersContainer);
identifiersContainer.getValue().addAll(Item.cloneCollection(entitlementIdentifiers));

// Remember the full shadow in user data. This is used later as an optimization to create the shadow in repo
identifiersContainer.setUserData(ResourceObjectConverter.FULL_SHADOW_KEY, entitlementShadow);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Processed entitlement-to-subject association for account {} and entitlement {}",
ShadowUtil.getHumanReadableName(resourceObject), ShadowUtil.getHumanReadableName(entitlementShadow));
}
return true;
} catch (SchemaException e) {
throw new TunnelException(e);
}
return true;
};

ConnectorInstance connector = subjectCtx.getConnector(ReadCapabilityType.class, parentResult);
Expand All @@ -267,8 +260,8 @@ public boolean handle(PrismObject<ShadowType> entitlementShadow) {
ShadowUtil.getHumanReadableName(resourceObject), query);
}
try {
connector.search(entitlementDef, query, handler, attributesToReturn, null, searchHierarchyConstraints,
FetchErrorReportingMethodType.DEFAULT, subjectCtx, parentResult);
connector.search(entitlementDef, query, handler, attributesToReturn, null,
searchHierarchyConstraints, UcfFetchErrorReportingMethod.EXCEPTION, subjectCtx, parentResult);
} catch (GenericFrameworkException e) {
throw new GenericConnectorException("Generic error in the connector " + connector + ". Reason: "
+ e.getMessage(), e);
Expand Down Expand Up @@ -443,49 +436,45 @@ public <T> void collectEntitlementsAsObjectOperationDelete(ProvisioningContext s

SearchHierarchyConstraints searchHierarchyConstraints = determineSearchHierarchyConstraints(entitlementCtx, parentResult);

ShadowResultHandler handler = new ShadowResultHandler() {
@Override
public boolean handle(PrismObject<ShadowType> entitlementShadow) {
Collection<? extends ResourceAttribute<?>> primaryIdentifiers = ShadowUtil.getPrimaryIdentifiers(entitlementShadow);
ResourceObjectDiscriminator disc = new ResourceObjectDiscriminator(entitlementOcDef.getTypeName(), primaryIdentifiers);
ResourceObjectOperations operations = roMap.get(disc);
if (operations == null) {
operations = new ResourceObjectOperations();
roMap.put(disc, operations);
operations.setResourceObjectContext(entitlementCtx);
Collection<? extends ResourceAttribute<?>> allIdentifiers = ShadowUtil.getAllIdentifiers(entitlementShadow);
operations.setAllIdentifiers(allIdentifiers);
}
FetchedObjectHandler handler = ucfObject -> {
PrismObject<ShadowType> entitlementShadow = ucfObject.getResourceObject();
Collection<? extends ResourceAttribute<?>> primaryIdentifiers = ShadowUtil.getPrimaryIdentifiers(entitlementShadow);
ResourceObjectDiscriminator disc = new ResourceObjectDiscriminator(entitlementOcDef.getTypeName(), primaryIdentifiers);
ResourceObjectOperations operations = roMap.get(disc);
if (operations == null) {
operations = new ResourceObjectOperations();
roMap.put(disc, operations);
operations.setResourceObjectContext(entitlementCtx);
Collection<? extends ResourceAttribute<?>> allIdentifiers = ShadowUtil.getAllIdentifiers(entitlementShadow);
operations.setAllIdentifiers(allIdentifiers);
}

PropertyDelta<T> attributeDelta = null;
for(Operation operation: operations.getOperations()) {
if (operation instanceof PropertyModificationOperation) {
PropertyModificationOperation propOp = (PropertyModificationOperation)operation;
if (propOp.getPropertyDelta().getElementName().equals(assocAttrName)) {
attributeDelta = propOp.getPropertyDelta();
}
PropertyDelta<T> attributeDelta = null;
for(Operation operation: operations.getOperations()) {
if (operation instanceof PropertyModificationOperation) {
PropertyModificationOperation propOp = (PropertyModificationOperation)operation;
if (propOp.getPropertyDelta().getElementName().equals(assocAttrName)) {
attributeDelta = propOp.getPropertyDelta();
}
}
if (attributeDelta == null) {
attributeDelta = assocAttrDef.createEmptyDelta(ItemPath.create(ShadowType.F_ATTRIBUTES, assocAttrName));
PropertyModificationOperation attributeModification = new PropertyModificationOperation(attributeDelta);
attributeModification.setMatchingRuleQName(assocDefType.getMatchingRule());
operations.add(attributeModification);
}
}
if (attributeDelta == null) {
attributeDelta = assocAttrDef.createEmptyDelta(ItemPath.create(ShadowType.F_ATTRIBUTES, assocAttrName));
PropertyModificationOperation attributeModification = new PropertyModificationOperation(attributeDelta);
attributeModification.setMatchingRuleQName(assocDefType.getMatchingRule());
operations.add(attributeModification);
}

attributeDelta.addValuesToDelete(valueAttr.getClonedValues());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Association in deleted shadow delta:\n{}", attributeDelta.debugDump());
}
attributeDelta.addValuesToDelete(valueAttr.getClonedValues());
LOGGER.trace("Association in deleted shadow delta:\n{}", attributeDelta.debugDumpLazily());

return true;
}
return true;
};
try {
LOGGER.trace("Searching for associations in deleted shadow, query: {}", query);
ConnectorInstance connector = subjectCtx.getConnector(ReadCapabilityType.class, parentResult);
connector.search(entitlementOcDef, query, handler, attributesToReturn, null, searchHierarchyConstraints,
FetchErrorReportingMethodType.DEFAULT, subjectCtx, parentResult);
connector.search(entitlementOcDef, query, handler, attributesToReturn, null,
searchHierarchyConstraints, UcfFetchErrorReportingMethod.EXCEPTION, subjectCtx, parentResult);
} catch (TunnelException e) {
throw (SchemaException)e.getCause();
} catch (GenericFrameworkException e) {
Expand Down
Expand Up @@ -175,19 +175,16 @@ public PrismObject<ShadowType> locateResourceObject(ProvisioningContext ctx,
.itemWithDef(secondaryIdentifierDef, ShadowType.F_ATTRIBUTES, secondaryIdentifierDef.getItemName()).eq(secondaryIdentifierValue)
.build();
final Holder<PrismObject<ShadowType>> shadowHolder = new Holder<>();
ShadowResultHandler handler = new ShadowResultHandler() {
@Override
public boolean handle(PrismObject<ShadowType> shadow) {
if (!shadowHolder.isEmpty()) {
throw new IllegalStateException("More than one value found for secondary identifier "+finalSecondaryIdentifier);
}
shadowHolder.setValue(shadow);
return true;
FetchedObjectHandler handler = ucfObject -> {
if (!shadowHolder.isEmpty()) {
throw new IllegalStateException("More than one value found for secondary identifier "+finalSecondaryIdentifier);
}
shadowHolder.setValue(ucfObject.getResourceObject());
return true;
};
try {
connector.search(ctx.getObjectClassDefinition(), query, handler, attributesToReturn, null, null,
FetchErrorReportingMethodType.DEFAULT, ctx, parentResult);
UcfFetchErrorReportingMethod.EXCEPTION, ctx, parentResult);
if (shadowHolder.isEmpty()) {
throw new ObjectNotFoundException("No object found for secondary identifier "+secondaryIdentifier);
}
Expand Down Expand Up @@ -1285,11 +1282,20 @@ public SearchResultMetadata searchResourceObjects(final ProvisioningContext ctx,

AtomicInteger objectCounter = new AtomicInteger(0);

UcfFetchErrorReportingMethod ucfErrorReportingMethod;
if (errorReportingMethod == FetchErrorReportingMethodType.FETCH_RESULT) {
ucfErrorReportingMethod = UcfFetchErrorReportingMethod.UCF_OBJECT;
} else {
ucfErrorReportingMethod = UcfFetchErrorReportingMethod.EXCEPTION;
}

SearchResultMetadata metadata;
try {

metadata = connector.search(objectClassDef, query,
(shadow) -> {
(ucfObject) -> {
PrismObject<ShadowType> shadow = ucfObject.getResourceObjectWithFetchResult();

// in order to utilize the cache right from the beginning...
RepositoryCache.enterLocalCaches(cacheConfigurationManager);
try {
Expand Down Expand Up @@ -1317,7 +1323,6 @@ public SearchResultMetadata searchResourceObjects(final ProvisioningContext ctx,
OperationResult objResult = resultBuilder.build();
try {
postProcessResourceObjectRead(ctx, shadow, fetchAssociations, objResult);
Validate.notNull(shadow, "null shadow");
return resultHandler.handle(shadow, objResult);
} catch (Throwable t) {
objResult.recordFatalError(t);
Expand Down Expand Up @@ -1348,7 +1353,7 @@ public SearchResultMetadata searchResourceObjects(final ProvisioningContext ctx,
}
},
attributesToReturn, objectClassDef.getPagedSearches(ctx.getResource()), searchHierarchyConstraints,
errorReportingMethod, ctx, parentResult);
ucfErrorReportingMethod, ctx, parentResult);

} catch (GenericFrameworkException e) {
parentResult.recordFatalError("Generic error in the connector: " + e.getMessage(), e);
Expand Down
Expand Up @@ -2057,7 +2057,7 @@ private SearchResultMetadata searchObjectIterativeResource(ProvisioningContext c
ExpressionEvaluationException, SecurityViolationException {

boolean isDoDiscovery = ProvisioningUtil.isDoDiscovery(ctx.getResource(), rootOptions);
FetchErrorReportingMethodType errorReportingMethod = getErrorReportingMethod(rootOptions);
FetchErrorReportingMethodType ucfErrorReportingMethod = getErrorReportingMethod(rootOptions);

// We need to record the fetch down here. Now it is certain that we are
// going to fetch from resource
Expand All @@ -2075,10 +2075,10 @@ private SearchResultMetadata searchObjectIterativeResource(ProvisioningContext c
resultShadow = treatObjectFound(ctx, updateRepository, isDoDiscovery, resourceObject, objResult);
} catch (Throwable t) {
objResult.recordFatalError(t.getMessage(), t);
if (errorReportingMethod == EXCEPTION) {
if (ucfErrorReportingMethod == EXCEPTION) {
// No need to log the exception. It will be dumped when caught and processed in upper layers.
throw new TunnelException(t);
} else if (errorReportingMethod == FETCH_RESULT) {
} else if (ucfErrorReportingMethod == FETCH_RESULT) {
resultShadow = resourceObject;
LOGGER.error("An error occurred while processing resource object {}. Recording it into object fetch result: {}",
resourceObject, t.getMessage(), t);
Expand Down Expand Up @@ -2109,7 +2109,7 @@ private SearchResultMetadata searchObjectIterativeResource(ProvisioningContext c
boolean fetchAssociations = SelectorOptions.hasToLoadPath(ShadowType.F_ASSOCIATION, options);
try {
return resourceObjectConverter.searchResourceObjects(ctx, resultHandler, attributeQuery,
fetchAssociations, errorReportingMethod, parentResult);
fetchAssociations, ucfErrorReportingMethod, parentResult);
} catch (TunnelException e) {
Throwable cause = e.getCause();
if (cause instanceof ObjectNotFoundException) {
Expand Down
Expand Up @@ -24,7 +24,6 @@
import com.evolveum.midpoint.schema.statistics.ConnectorOperationalStatus;
import com.evolveum.midpoint.task.api.StateReporter;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.xml.ns._public.common.common_3.FetchErrorReportingMethodType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.PagedSearchCapabilityType;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -204,8 +203,8 @@ PrismObject<ShadowType> fetchObject(ResourceObjectIdentification resourceObjectI
* e.g. if search base points to an non-existent object.
*/
SearchResultMetadata search(ObjectClassComplexTypeDefinition objectClassDefinition, ObjectQuery query,
ShadowResultHandler handler, AttributesToReturn attributesToReturn, PagedSearchCapabilityType pagedSearchConfiguration,
SearchHierarchyConstraints searchHierarchyConstraints, FetchErrorReportingMethodType errorReportingMethod,
FetchedObjectHandler handler, AttributesToReturn attributesToReturn, PagedSearchCapabilityType pagedSearchConfiguration,
SearchHierarchyConstraints searchHierarchyConstraints, UcfFetchErrorReportingMethod errorReportingMethod,
StateReporter reporter, OperationResult parentResult)
throws CommunicationException, GenericFrameworkException, SchemaException, SecurityViolationException,
ObjectNotFoundException;
Expand Down
Expand Up @@ -6,9 +6,6 @@
*/
package com.evolveum.midpoint.provisioning.ucf.api;

import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType;

/**
* Classes implementing this interface are used to handle iterative results.
*
Expand All @@ -18,13 +15,13 @@
* @author Radovan Semancik
*/
@FunctionalInterface
public interface ShadowResultHandler {
public interface FetchedObjectHandler {

/**
* Handle a single result.
* @param object Resource object to process.
* @return true if the operation should proceed, false if it should stop
*/
boolean handle(PrismObject<ShadowType> object);
boolean handle(FetchedUcfObject object);

}

0 comments on commit 0f08d64

Please sign in to comment.