/*
* Copyright (c) 2010-2017 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.evolveum.midpoint.provisioning.impl;
import com.evolveum.midpoint.common.Clock;
import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition;
import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.common.refinery.ShadowDiscriminatorObjectDelta;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.Visitor;
import com.evolveum.midpoint.prism.delta.*;
import com.evolveum.midpoint.prism.match.MatchingRuleRegistry;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.prism.query.*;
import com.evolveum.midpoint.prism.xml.XmlTypeConverter;
import com.evolveum.midpoint.provisioning.api.*;
import com.evolveum.midpoint.provisioning.consistency.api.ErrorHandler;
import com.evolveum.midpoint.provisioning.consistency.api.ErrorHandler.FailedOperation;
import com.evolveum.midpoint.provisioning.consistency.impl.ErrorHandlerFactory;
import com.evolveum.midpoint.provisioning.ucf.api.Change;
import com.evolveum.midpoint.provisioning.ucf.api.ConnectorInstance;
import com.evolveum.midpoint.provisioning.ucf.api.GenericFrameworkException;
import com.evolveum.midpoint.provisioning.ucf.api.ResultHandler;
import com.evolveum.midpoint.provisioning.util.ProvisioningUtil;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.*;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.internals.InternalMonitor;
import com.evolveum.midpoint.schema.internals.InternalsConfig;
import com.evolveum.midpoint.schema.processor.*;
import com.evolveum.midpoint.schema.result.AsynchronousOperationResult;
import com.evolveum.midpoint.schema.result.AsynchronousOperationReturnValue;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.result.OperationResultStatus;
import com.evolveum.midpoint.schema.util.ObjectQueryUtil;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
import com.evolveum.midpoint.schema.util.SchemaDebugUtil;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.Holder;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CountObjectsCapabilityType;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.CountObjectsSimulateType;
import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.ReadCapabilityType;
import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import javax.xml.datatype.DatatypeConstants;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Shadow cache is a facade that covers all the operations with shadows. It
* takes care of splitting the operations between repository and resource,
* merging the data back, handling the errors and generally controlling the
* process.
*
* The two principal classes that do the operations are:
* ResourceObjectConvertor: executes operations on resource ShadowManager:
* executes operations in the repository
*
* @author Radovan Semancik
* @author Katarina Valalikova
*
*/
public abstract class ShadowCache {
public static String OP_PROCESS_SYNCHRONIZATION = ShadowCache.class.getName() + ".processSynchronization";
@Autowired(required = true)
@Qualifier("cacheRepositoryService")
private RepositoryService repositoryService;
@Autowired(required = true)
private ErrorHandlerFactory errorHandlerFactory;
@Autowired(required = true)
private ResourceManager resourceManager;
@Autowired(required = true)
private Clock clock;
@Autowired(required = true)
private PrismContext prismContext;
@Autowired(required = true)
private ResourceObjectConverter resouceObjectConverter;
@Autowired(required = true)
private MatchingRuleRegistry matchingRuleRegistry;
@Autowired(required = true)
protected ShadowManager shadowManager;
@Autowired(required = true)
private ChangeNotificationDispatcher operationListener;
@Autowired(required = true)
private AccessChecker accessChecker;
@Autowired(required = true)
private TaskManager taskManager;
@Autowired(required = true)
private ChangeNotificationDispatcher changeNotificationDispatcher;
@Autowired(required = true)
private ProvisioningContextFactory ctxFactory;
private static final Trace LOGGER = TraceManager.getTrace(ShadowCache.class);
public ShadowCache() {
repositoryService = null;
}
/**
* Get the value of repositoryService.
*
* DO NOT USE. Only ShadowManager shoudl access repository
*
* @return the value of repositoryService
*/
@Deprecated
public RepositoryService getRepositoryService() {
return repositoryService;
}
public PrismContext getPrismContext() {
return prismContext;
}
public PrismObject<ShadowType> getShadow(String oid, PrismObject<ShadowType> repositoryShadow,
Collection<SelectorOptions<GetOperationOptions>> options, Task task, OperationResult parentResult)
throws ObjectNotFoundException, CommunicationException, SchemaException,
ConfigurationException, SecurityViolationException {
Validate.notNull(oid, "Object id must not be null.");
if (repositoryShadow == null) {
LOGGER.trace("Start getting object with oid {}", oid);
} else {
LOGGER.trace("Start getting object {}", repositoryShadow);
}
GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options);
// We are using parent result directly, not creating subresult.
// We want to hide the existence of shadow cache from the user.
// Get the shadow from repository. There are identifiers that we need
// for accessing the object by UCF.Later, the repository object may
// have a fully cached object from the resource.
if (repositoryShadow == null) {
repositoryShadow = repositoryService.getObject(ShadowType.class, oid, null, parentResult);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Got repository shadow object:\n{}", repositoryShadow.debugDump());
}
}
// Sanity check
if (!oid.equals(repositoryShadow.getOid())) {
parentResult.recordFatalError("Provided OID is not equal to OID of repository shadow");
throw new IllegalArgumentException("Provided OID is not equal to OID of repository shadow");
}
ProvisioningContext ctx = ctxFactory.create(repositoryShadow, task, parentResult);
ctx.setGetOperationOptions(options);
try {
ctx.assertDefinition();
applyAttributesDefinition(ctx, repositoryShadow);
} catch (ObjectNotFoundException | SchemaException | CommunicationException
| ConfigurationException e) {
throw e;
}
ResourceType resource = ctx.getResource();
if (GetOperationOptions.isNoFetch(rootOptions) || GetOperationOptions.isRaw(rootOptions)) {
return processNoFetchGet(ctx, repositoryShadow, options, parentResult);
}
if (!ResourceTypeUtil.isReadCapabilityEnabled(resource)) {
UnsupportedOperationException e = new UnsupportedOperationException("Resource does not support 'read' operation");
parentResult.recordFatalError(e);
throw e;
}
repositoryShadow = refreshShadow(repositoryShadow, task, parentResult);
if (repositoryShadow == null) {
// Dead shadow was just removed
// TODO: is this OK? What about re-appeared objects
ObjectNotFoundException e = new ObjectNotFoundException("Resource object does not exist");
parentResult.recordFatalError(e);
throw e;
}
if (canReturnCached(options, repositoryShadow, resource)) {
PrismObject<ShadowType> resultShadow = futurizeShadow(repositoryShadow, options, resource);
applyAttributesDefinition(ctx, resultShadow);
return resultShadow;
}
PrismObject<ShadowType> resourceShadow = null;
try {
Collection<? extends ResourceAttribute<?>> primaryIdentifiers = ShadowUtil
.getPrimaryIdentifiers(repositoryShadow);
if (primaryIdentifiers == null || primaryIdentifiers.isEmpty()) {
// check if the account is not only partially created (exist
// only in repo so far)
if (repositoryShadow.asObjectable().getFailedOperationType() != null) {
throw new GenericConnectorException(
"Unable to get object from the resource. Probably it has not been created yet because of previous unavailability of the resource.");
}
// No identifiers found
SchemaException ex = new SchemaException("No primary identifiers found in the repository shadow "
+ repositoryShadow + " with respect to " + resource);
parentResult.recordFatalError(
"No prmary identifiers found in the repository shadow " + repositoryShadow, ex);
throw ex;
}
Collection<? extends ResourceAttribute<?>> identifiers = ShadowUtil
.getAllIdentifiers(repositoryShadow);
try {
resourceShadow = resouceObjectConverter.getResourceObject(ctx, identifiers, true, parentResult);
} catch (ObjectNotFoundException e) {
// This may be OK, e.g. for connectors that have running async add operation.
if (canReturnCachedAfterNotFoundOnResource(options, repositoryShadow, resource)) {
LOGGER.trace("Object not found on reading of {}, but we can return cached shadow", repositoryShadow);
parentResult.muteLastSubresultError();
parentResult.recordSuccess();
repositoryShadow.asObjectable().setExists(false);
PrismObject<ShadowType> resultShadow = futurizeShadow(repositoryShadow, options, resource);
applyAttributesDefinition(ctx, resultShadow);
return resultShadow;
} else {
throw e;
}
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Shadow returned by ResouceObjectConverter:\n{}", resourceShadow.debugDump(1));
}
// Resource shadow may have different auxiliary object classes than
// the original repo shadow. Make sure we have the definition that
// applies to resource shadow. We will fix repo shadow later.
// BUT we need also information about kind/intent and these information is only
// in repo shadow, therefore the following 2 lines..
resourceShadow.asObjectable().setKind(repositoryShadow.asObjectable().getKind());
resourceShadow.asObjectable().setIntent(repositoryShadow.asObjectable().getIntent());
ProvisioningContext shadowCtx = ctx.spawn(resourceShadow);
resourceManager.modifyResourceAvailabilityStatus(resource.asPrismObject(),
AvailabilityStatusType.UP, parentResult);
// try to apply changes to the account only if the resource if UP
if (isCompensate(rootOptions) && repositoryShadow.asObjectable().getObjectChange() != null
&& repositoryShadow.asObjectable().getFailedOperationType() != null
&& resource.getOperationalState() != null && resource.getOperationalState()
.getLastAvailabilityStatus() == AvailabilityStatusType.UP) {
throw new GenericConnectorException(
"Found changes that have been not applied to the resource object yet. Trying to apply them now.");
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Shadow from repository:\n{}", repositoryShadow.debugDump());
LOGGER.trace("Resource object fetched from resource:\n{}", resourceShadow.debugDump());
}
repositoryShadow = shadowManager.updateShadow(shadowCtx, resourceShadow, repositoryShadow,
parentResult);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Repository shadow after update:\n{}", repositoryShadow.debugDump());
}
// Complete the shadow by adding attributes from the resource object
PrismObject<ShadowType> resultShadow = completeShadow(shadowCtx, resourceShadow, repositoryShadow,
parentResult);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Shadow when assembled:\n{}", resultShadow.debugDump());
}
resultShadow = futurizeShadow(resultShadow, options, resource);
parentResult.recordSuccess();
return resultShadow;
} catch (Exception ex) {
try {
resourceShadow = handleError(ctx, ex, repositoryShadow, FailedOperation.GET, null,
isDoDiscovery(resource, rootOptions), isCompensate(rootOptions), parentResult);
if (parentResult.getStatus() == OperationResultStatus.FATAL_ERROR) {
// We are going to return an object. Therefore this cannot
// be fatal error, as at least some information
// is returned
parentResult.setStatus(OperationResultStatus.PARTIAL_ERROR);
}
return resourceShadow;
} catch (GenericFrameworkException e) {
throw new SystemException(e.getMessage(), e);
} catch (ObjectAlreadyExistsException e) {
throw new SystemException(e.getMessage(), e);
}
} finally {
// We need to record the fetch down here. Now it is certain that we
// are going to fetch from resource
// (we do not have raw/noFetch option)
// TODO: is this correct behaviour? don't we really want to record
// fetch for protected objects?
if (!ShadowUtil.isProtected(resourceShadow)) {
InternalMonitor.recordShadowFetchOperation();
}
}
}
private PrismObject<ShadowType> processNoFetchGet(ProvisioningContext ctx, PrismObject<ShadowType> repositoryShadow,
Collection<SelectorOptions<GetOperationOptions>> options, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException {
ResourceType resource = ctx.getResource();
ShadowType repositoryShadowType = repositoryShadow.asObjectable();
LOGGER.trace("Processing noFetch get for {}", repositoryShadow);
// Even with noFetch we still want to delete expired pending operations. And even delete
// the shadow if needed.
ObjectDelta<ShadowType> shadowDelta = repositoryShadow.createModifyDelta();
XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar();
boolean atLeastOnePendingOperationRemains = expirePendingOperations(ctx, repositoryShadow, shadowDelta, now, parentResult);
if (repositoryShadowType.getFailedOperationType() != null) {
atLeastOnePendingOperationRemains = true;
}
if (ShadowUtil.isDead(repositoryShadowType) && !atLeastOnePendingOperationRemains) {
LOGGER.trace("Removing dead shadow with no pending operation: {}", repositoryShadow);
shadowManager.deleteShadow(ctx, repositoryShadow, null, parentResult);
ObjectNotFoundException e = new ObjectNotFoundException("Resource object not found");
parentResult.recordFatalError(e);
throw e;
}
if (!shadowDelta.isEmpty()) {
shadowManager.modifyShadowAttributes(ctx, repositoryShadow, shadowDelta.getModifications(), parentResult);
shadowDelta.applyTo(repositoryShadow);
}
PrismObject<ShadowType> resultShadow = futurizeShadow(repositoryShadow, options, resource);
applyAttributesDefinition(ctx, resultShadow);
return resultShadow;
}
private PrismObject<ShadowType> futurizeShadow(PrismObject<ShadowType> shadow,
Collection<SelectorOptions<GetOperationOptions>> options, ResourceType resource) throws SchemaException {
PointInTimeType pit = GetOperationOptions.getPointInTimeType(SelectorOptions.findRootOptions(options));
if (pit != PointInTimeType.FUTURE) {
return shadow;
}
PrismObject<ShadowType> resultShadow = shadow;
ShadowType resultShadowType = resultShadow.asObjectable();
List<PendingOperationType> sortedOperations = sortOperations(resultShadowType.getPendingOperation());
boolean resourceReadIsCachingOnly = resourceReadIsCahingOnly(resource);
for (PendingOperationType pendingOperation: sortedOperations) {
OperationResultStatusType resultStatus = pendingOperation.getResultStatus();
if (resultStatus == OperationResultStatusType.FATAL_ERROR || resultStatus == OperationResultStatusType.NOT_APPLICABLE) {
continue;
}
if (resourceReadIsCachingOnly) {
// We are getting the data from our own cache. So we know that all completed operations are already applied in the cache.
// Re-applying them will mean additional risk of corrupting the data.
if (resultStatus != null && resultStatus != OperationResultStatusType.IN_PROGRESS && resultStatus != OperationResultStatusType.UNKNOWN) {
continue;
}
} else {
// We want to apply all the deltas, even those that are already completed. They might not be reflected on the resource yet.
// E.g. they may be not be present in the CSV export until the next export cycle is scheduled
}
ObjectDeltaType pendingDeltaType = pendingOperation.getDelta();
ObjectDelta<ShadowType> pendingDelta = DeltaConvertor.createObjectDelta(pendingDeltaType, prismContext);
if (pendingDelta.isAdd()) {
if (resultShadowType.isExists() == Boolean.FALSE) {
resultShadow = pendingDelta.getObjectToAdd().clone();
resultShadow.setOid(shadow.getOid());
resultShadowType = resultShadow.asObjectable();
resultShadowType.setExists(true);
resultShadowType.setName(shadow.asObjectable().getName());
}
}
if (pendingDelta.isModify()) {
pendingDelta.applyTo(resultShadow);
}
if (pendingDelta.isDelete()) {
resultShadowType.setDead(true);
resultShadowType.setExists(false);
}
}
// TODO: check schema, remove non-readable attributes, activation, password, etc.
// CredentialsType creds = resultShadowType.getCredentials();
// if (creds != null) {
// PasswordType passwd = creds.getPassword();
// if (passwd != null) {
// passwd.setValue(null);
// }
// }
return resultShadow;
}
private boolean resourceReadIsCahingOnly(ResourceType resource) {
ReadCapabilityType readCapabilityType = ResourceTypeUtil.getEffectiveCapability(resource, ReadCapabilityType.class);
Boolean cachingOnly = readCapabilityType.isCachingOnly();
if (cachingOnly == Boolean.TRUE) {
return true;
}
return false;
}
private boolean canReturnCachedAfterNotFoundOnResource(Collection<SelectorOptions<GetOperationOptions>> options, PrismObject<ShadowType> repositoryShadow, ResourceType resource) throws ConfigurationException {
if (repositoryShadow.asObjectable().getPendingOperation().isEmpty()) {
return false;
}
// Explicitly check the capability of the resource (primary connector), not capabilities of additional connectors
ReadCapabilityType readCapabilityType = CapabilityUtil.getEffectiveCapability(resource.getCapabilities(), ReadCapabilityType.class);
if (readCapabilityType == null) {
return false;
}
if (!CapabilityUtil.isCapabilityEnabled(readCapabilityType)) {
return false;
}
Boolean cachingOnly = readCapabilityType.isCachingOnly();
if (cachingOnly == Boolean.TRUE) {
return true;
}
return false;
}
private boolean canReturnCached(Collection<SelectorOptions<GetOperationOptions>> options, PrismObject<ShadowType> repositoryShadow, ResourceType resource) throws ConfigurationException {
if (resourceReadIsCahingOnly(resource)) {
return true;
}
PointInTimeType pit = GetOperationOptions.getPointInTimeType(SelectorOptions.findRootOptions(options));
if (pit != null) {
if (pit != PointInTimeType.CACHED) {
return false;
}
}
long stalenessOption = GetOperationOptions.getStaleness(SelectorOptions.findRootOptions(options));
if (stalenessOption == 0L) {
return false;
}
CachingMetadataType cachingMetadata = repositoryShadow.asObjectable().getCachingMetadata();
if (cachingMetadata == null) {
if (stalenessOption == Long.MAX_VALUE) {
// We must return cached version but there is no cached version.
throw new ConfigurationException("Cached version of "+repositoryShadow+" requested, but there is no cached value");
}
return false;
}
if (stalenessOption == Long.MAX_VALUE) {
return true;
}
XMLGregorianCalendar retrievalTimestamp = cachingMetadata.getRetrievalTimestamp();
if (retrievalTimestamp == null) {
return false;
}
long retrievalTimestampMillis = XmlTypeConverter.toMillis(retrievalTimestamp);
return (clock.currentTimeMillis() - retrievalTimestampMillis < stalenessOption);
}
private boolean isCompensate(GetOperationOptions rootOptions) {
return !GetOperationOptions.isDoNotDiscovery(rootOptions);
}
private boolean isCompensate(ProvisioningOperationOptions options) {
return ProvisioningOperationOptions.isCompletePostponed(options);
}
private boolean isDoDiscovery(ResourceType resource, GetOperationOptions rootOptions) {
return !GetOperationOptions.isDoNotDiscovery(rootOptions) && isDoDiscovery(resource);
}
private boolean isDoDiscovery(ResourceType resource, ProvisioningOperationOptions options) {
return !ProvisioningOperationOptions.isDoNotDiscovery(options) && isDoDiscovery(resource);
}
private boolean isDoDiscovery (ResourceType resource) {
if (resource == null) {
return true;
}
if (resource.getConsistency() == null) {
return true;
}
if (resource.getConsistency().isDiscovery() == null) {
return true;
}
return resource.getConsistency().isDiscovery();
}
public abstract String afterAddOnResource(ProvisioningContext ctx, AsynchronousOperationReturnValue<PrismObject<ShadowType>> addResult,
OperationResult parentResult) throws SchemaException, ObjectAlreadyExistsException,
ObjectNotFoundException, ConfigurationException, CommunicationException;
public String addShadow(PrismObject<ShadowType> shadow, OperationProvisioningScriptsType scripts,
ResourceType resource, ProvisioningOperationOptions options, Task task,
OperationResult parentResult) throws CommunicationException, GenericFrameworkException,
ObjectAlreadyExistsException, SchemaException, ObjectNotFoundException,
ConfigurationException, SecurityViolationException {
Validate.notNull(shadow, "Object to add must not be null.");
InternalMonitor.recordShadowChangeOperation();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Start adding shadow object:\n{}", shadow.debugDump());
}
ProvisioningContext ctx = ctxFactory.create(shadow, task, parentResult);
try {
ctx.assertDefinition();
} catch (SchemaException e) {
handleError(ctx, e, shadow, FailedOperation.ADD, null,
isDoDiscovery(resource, options), true, parentResult);
return null;
}
// if (LOGGER.isTraceEnabled()) {
// LOGGER.trace("Definition:\n{}", ctx.getObjectClassDefinition().debugDump());
// }
PrismContainer<?> attributesContainer = shadow.findContainer(ShadowType.F_ATTRIBUTES);
if (attributesContainer == null || attributesContainer.isEmpty()) {
SchemaException e = new SchemaException(
"Attempt to add shadow without any attributes: " + shadow);
parentResult.recordFatalError(e);
handleError(ctx, e, shadow, FailedOperation.ADD, null,
isDoDiscovery(resource, options), true, parentResult);
return null;
}
AsynchronousOperationReturnValue<PrismObject<ShadowType>> asyncReturnValue;
PrismObject<ShadowType> addedShadow;
try {
preprocessEntitlements(ctx, shadow, parentResult);
applyAttributesDefinition(ctx, shadow);
shadowManager.setKindIfNecessary(shadow.asObjectable(), ctx.getObjectClassDefinition());
accessChecker.checkAdd(ctx, shadow, parentResult);
// RESOURCE OPERATION: add
asyncReturnValue =
resouceObjectConverter.addResourceObject(ctx, shadow, scripts, parentResult);
addedShadow = asyncReturnValue.getReturnValue();
} catch (Exception ex) {
addedShadow = handleError(ctx, ex, shadow, FailedOperation.ADD, null,
isDoDiscovery(resource, options), isCompensate(options), parentResult);
return addedShadow.getOid();
}
// REPO OPERATION: add
// This is where the repo shadow is created (if needed)
String oid = afterAddOnResource(ctx, asyncReturnValue, parentResult);
addedShadow.setOid(oid);
ObjectDelta<ShadowType> delta = ObjectDelta.createAddDelta(addedShadow);
ResourceOperationDescription operationDescription = createSuccessOperationDescription(ctx, addedShadow,
delta, parentResult);
if (asyncReturnValue.isInProgress()) {
operationListener.notifyInProgress(operationDescription, task, parentResult);
} else {
operationListener.notifySuccess(operationDescription, task, parentResult);
}
return oid;
}
private ResourceOperationDescription createSuccessOperationDescription(ProvisioningContext ctx,
PrismObject<ShadowType> shadowType, ObjectDelta delta, OperationResult parentResult)
throws ObjectNotFoundException, SchemaException, CommunicationException,
ConfigurationException {
ResourceOperationDescription operationDescription = new ResourceOperationDescription();
operationDescription.setCurrentShadow(shadowType);
operationDescription.setResource(ctx.getResource().asPrismObject());
if (ctx.getTask() != null) {
operationDescription.setSourceChannel(ctx.getTask().getChannel());
}
operationDescription.setObjectDelta(delta);
operationDescription.setResult(parentResult);
return operationDescription;
}
public abstract void afterModifyOnResource(ProvisioningContext ctx, PrismObject<ShadowType> shadow,
Collection<? extends ItemDelta> modifications, OperationResult resourceOperationResult, OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, ConfigurationException,
CommunicationException;
public abstract Collection<? extends ItemDelta> beforeModifyOnResource(PrismObject<ShadowType> shadow,
ProvisioningOperationOptions options, Collection<? extends ItemDelta> modifications)
throws SchemaException;
public String modifyShadow(PrismObject<ShadowType> repoShadow, String oid,
Collection<? extends ItemDelta> modifications, OperationProvisioningScriptsType scripts,
ProvisioningOperationOptions options, Task task, OperationResult parentResult)
throws CommunicationException, GenericFrameworkException, ObjectNotFoundException,
SchemaException, ConfigurationException, SecurityViolationException {
Validate.notNull(repoShadow, "Object to modify must not be null.");
Validate.notNull(oid, "OID must not be null.");
Validate.notNull(modifications, "Object modification must not be null.");
InternalMonitor.recordShadowChangeOperation();
Collection<QName> additionalAuxiliaryObjectClassQNames = new ArrayList<>();
ItemPath auxPath = new ItemPath(ShadowType.F_AUXILIARY_OBJECT_CLASS);
for (ItemDelta modification : modifications) {
if (auxPath.equals(modification.getPath())) {
PropertyDelta<QName> auxDelta = (PropertyDelta<QName>) modification;
for (PrismPropertyValue<QName> pval : auxDelta.getValues(QName.class)) {
additionalAuxiliaryObjectClassQNames.add(pval.getValue());
}
}
}
ProvisioningContext ctx = ctxFactory.create(repoShadow, additionalAuxiliaryObjectClassQNames, task,
parentResult);
AsynchronousOperationReturnValue<Collection<PropertyDelta<PrismPropertyValue>>> asyncReturnValue;
try {
ctx.assertDefinition();
RefinedObjectClassDefinition rOCDef = ctx.getObjectClassDefinition();
applyAttributesDefinition(ctx, repoShadow);
accessChecker.checkModify(ctx.getResource(), repoShadow, modifications,
ctx.getObjectClassDefinition(), parentResult);
modifications = beforeModifyOnResource(repoShadow, options, modifications);
preprocessEntitlements(ctx, modifications, "delta for shadow " + oid, parentResult);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Applying change: {}", DebugUtil.debugDump(modifications));
}
asyncReturnValue = resouceObjectConverter.modifyResourceObject(ctx, repoShadow, scripts,
modifications, parentResult);
} catch (Exception ex) {
LOGGER.debug("Provisioning exception: {}:{}, attempting to handle it",
new Object[] { ex.getClass(), ex.getMessage(), ex });
try {
repoShadow = handleError(ctx, ex, repoShadow, FailedOperation.MODIFY, modifications,
isDoDiscovery(ctx.getResource(), options), isCompensate(options), parentResult);
parentResult.computeStatus();
} catch (ObjectAlreadyExistsException e) {
parentResult.recordFatalError(
"While compensating communication problem for modify operation got: "
+ ex.getMessage(),
ex);
throw new SystemException(e);
}
return repoShadow.getOid();
}
Collection<PropertyDelta<PrismPropertyValue>> sideEffectChanges = asyncReturnValue.getReturnValue();
if (sideEffectChanges != null) {
ItemDelta.addAll(modifications, sideEffectChanges);
}
afterModifyOnResource(ctx, repoShadow, modifications, asyncReturnValue.getOperationResult(), parentResult);
ObjectDelta<ShadowType> delta = ObjectDelta.createModifyDelta(repoShadow.getOid(), modifications,
repoShadow.getCompileTimeClass(), prismContext);
ResourceOperationDescription operationDescription = createSuccessOperationDescription(ctx, repoShadow,
delta, parentResult);
if (asyncReturnValue.isInProgress()) {
operationListener.notifyInProgress(operationDescription, task, parentResult);
parentResult.recordInProgress();
parentResult.setAsynchronousOperationReference(asyncReturnValue.getOperationResult().getAsynchronousOperationReference());
} else {
operationListener.notifySuccess(operationDescription, task, parentResult);
parentResult.recordSuccess();
}
return oid;
}
public void deleteShadow(PrismObject<ShadowType> shadow, ProvisioningOperationOptions options,
OperationProvisioningScriptsType scripts, Task task, OperationResult parentResult)
throws CommunicationException, GenericFrameworkException, ObjectNotFoundException,
SchemaException, ConfigurationException, SecurityViolationException {
Validate.notNull(shadow, "Object to delete must not be null.");
Validate.notNull(parentResult, "Operation result must not be null.");
InternalMonitor.recordShadowChangeOperation();
ProvisioningContext ctx = ctxFactory.create(shadow, task, parentResult);
try {
ctx.assertDefinition();
} catch (ObjectNotFoundException ex) {
// if the force option is set, delete shadow from the repo
// although the resource does not exists..
if (ProvisioningOperationOptions.isForce(options)) {
parentResult.muteLastSubresultError();
shadowManager.deleteShadow(ctx, shadow, null, parentResult);
parentResult.recordHandledError(
"Resource defined in shadow does not exists. Shadow was deleted from the repository.");
return;
} else {
throw ex;
}
}
applyAttributesDefinition(ctx, shadow);
LOGGER.trace("Deleting object {} from the resource {}.", shadow, ctx.getResource());
AsynchronousOperationResult asyncReturnValue = null;
if (shadow.asObjectable().getFailedOperationType() == null
|| (shadow.asObjectable().getFailedOperationType() != null
&& FailedOperationTypeType.ADD != shadow.asObjectable().getFailedOperationType())) {
try {
asyncReturnValue = resouceObjectConverter.deleteResourceObject(ctx, shadow, scripts, parentResult);
} catch (Exception ex) {
try {
handleError(ctx, ex, shadow, FailedOperation.DELETE, null,
isDoDiscovery(ctx.getResource(), options), isCompensate(options), parentResult);
} catch (ObjectAlreadyExistsException e) {
parentResult.recordFatalError(e);
throw new SystemException(e.getMessage(), e);
}
return;
}
}
LOGGER.trace("Detele object with oid {} form repository.", shadow.getOid());
try {
shadowManager.deleteShadow(ctx, shadow, asyncReturnValue==null?null:asyncReturnValue.getOperationResult(), parentResult);
} catch (ObjectNotFoundException ex) {
parentResult.recordFatalError("Can't delete object " + shadow + ". Reason: " + ex.getMessage(),
ex);
throw new ObjectNotFoundException("An error occured while deleting resource object " + shadow
+ "whith identifiers " + shadow + ": " + ex.getMessage(), ex);
}
ObjectDelta<ShadowType> delta = ObjectDelta.createDeleteDelta(shadow.getCompileTimeClass(),
shadow.getOid(), prismContext);
ResourceOperationDescription operationDescription = createSuccessOperationDescription(ctx, shadow,
delta, parentResult);
if (asyncReturnValue != null && asyncReturnValue.isInProgress()) {
operationListener.notifyInProgress(operationDescription, task, parentResult);
} else {
operationListener.notifySuccess(operationDescription, task, parentResult);
}
LOGGER.trace("Object deleted from repository successfully.");
parentResult.computeStatus();
resourceManager.modifyResourceAvailabilityStatus(ctx.getResource().asPrismObject(),
AvailabilityStatusType.UP, parentResult);
}
public PrismObject<ShadowType> refreshShadow(PrismObject<ShadowType> repoShadow, Task task, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException {
ShadowType shadowType = repoShadow.asObjectable();
List<PendingOperationType> pendingOperations = shadowType.getPendingOperation();
if (pendingOperations.isEmpty()) {
LOGGER.trace("Skipping refresh of {} because there are no pending operations", repoShadow);
return repoShadow;
}
LOGGER.trace("Refreshing {}", repoShadow);
ProvisioningContext ctx = ctxFactory.create(repoShadow, task, parentResult);
ctx.assertDefinition();
Duration gracePeriod = null;
ResourceConsistencyType consistency = ctx.getResource().getConsistency();
if (consistency != null) {
gracePeriod = consistency.getPendingOperationGracePeriod();
}
List<ObjectDelta<ShadowType>> notificationDeltas = new ArrayList<>();
List<PendingOperationType> sortedOperations = sortOperations(pendingOperations);
boolean isDead = ShadowUtil.isDead(shadowType);
ObjectDelta<ShadowType> shadowDelta = repoShadow.createModifyDelta();
for (PendingOperationType pendingOperation: sortedOperations) {
ItemPath containerPath = pendingOperation.asPrismContainerValue().getPath();
OperationResultStatusType statusType = pendingOperation.getResultStatus();
XMLGregorianCalendar completionTimestamp = pendingOperation.getCompletionTimestamp();
XMLGregorianCalendar now = null;
String asyncRef = pendingOperation.getAsynchronousOperationReference();
if (asyncRef != null) {
OperationResultStatus newStaus = resouceObjectConverter.refreshOperationStatus(ctx, repoShadow, asyncRef, parentResult);
now = clock.currentTimeXMLGregorianCalendar();
if (newStaus != null) {
OperationResultStatusType newStatusType = newStaus.createStatusType();
if (!newStatusType.equals(pendingOperation.getResultStatus())) {
boolean operationCompleted = isCompleted(newStatusType) && pendingOperation.getCompletionTimestamp() == null;
if (operationCompleted && gracePeriod == null) {
LOGGER.trace("Deleting pending operation because it is completed (no grace): {}", pendingOperation);
shadowDelta.addModificationDeleteContainer(new ItemPath(ShadowType.F_PENDING_OPERATION), pendingOperation.clone());
continue;
} else {
PropertyDelta<OperationResultStatusType> statusDelta = shadowDelta.createPropertyModification(containerPath.subPath(PendingOperationType.F_RESULT_STATUS));
statusDelta.setValuesToReplace(new PrismPropertyValue<>(newStatusType));
shadowDelta.addModification(statusDelta);
}
statusType = newStatusType;
if (operationCompleted) {
// Operation completed
PropertyDelta<XMLGregorianCalendar> timestampDelta = shadowDelta.createPropertyModification(containerPath.subPath(PendingOperationType.F_COMPLETION_TIMESTAMP));
timestampDelta.setValuesToReplace(new PrismPropertyValue<>(now));
shadowDelta.addModification(timestampDelta);
completionTimestamp = now;
ObjectDeltaType pendingDeltaType = pendingOperation.getDelta();
ObjectDelta<ShadowType> pendingDelta = DeltaConvertor.createObjectDelta(pendingDeltaType, prismContext);
if (pendingDelta.isAdd()) {
// We do not need to care about attributes in add deltas here. The add operation is already applied to
// attributes. We need this to "allocate" the identifiers, so iteration mechanism in the
// model can find unique values while taking pending create operations into consideration.
PropertyDelta<Boolean> existsDelta = shadowDelta.createPropertyModification(new ItemPath(ShadowType.F_EXISTS));
existsDelta.setValuesToReplace(new PrismPropertyValue<>(true));
shadowDelta.addModification(existsDelta);
}
if (pendingDelta.isModify()) {
for (ItemDelta<?, ?> pendingModification: pendingDelta.getModifications()) {
shadowDelta.addModification(pendingModification.clone());
}
}
if (pendingDelta.isDelete()) {
isDead = true;
if (gracePeriod == null) {
shadowDelta = repoShadow.createDeleteDelta();
notificationDeltas.add(pendingDelta);
break;
} else {
PropertyDelta<Boolean> deadDelta = shadowDelta.createPropertyModification(new ItemPath(ShadowType.F_DEAD));
deadDelta.setValuesToReplace(new PrismPropertyValue<>(true));
shadowDelta.addModification(deadDelta);
PropertyDelta<Boolean> existsDelta = shadowDelta.createPropertyModification(new ItemPath(ShadowType.F_EXISTS));
existsDelta.setValuesToReplace(new PrismPropertyValue<>(false));
shadowDelta.addModification(existsDelta);
}
}
notificationDeltas.add(pendingDelta);
}
}
}
}
if (now == null) {
now = clock.currentTimeXMLGregorianCalendar();
}
}
if (shadowDelta.isDelete()) {
LOGGER.trace("Deleting dead shadow because pending delete delta was completed (no grace period): {}", repoShadow);
shadowManager.deleteShadow(ctx, repoShadow, null, parentResult);
return null;
}
XMLGregorianCalendar now = clock.currentTimeXMLGregorianCalendar();
boolean atLeastOnePendingOperationRemains = expirePendingOperations(ctx, repoShadow, shadowDelta, now, parentResult);
if (shadowType.getFailedOperationType() != null) {
atLeastOnePendingOperationRemains = true;
}
if (isDead && !atLeastOnePendingOperationRemains) {
LOGGER.trace("Deleting dead shadow because all pending operations expired: {}", repoShadow);
shadowManager.deleteShadow(ctx, repoShadow, null, parentResult);
return null;
}
if (!shadowDelta.isEmpty()) {
shadowManager.modifyShadowAttributes(ctx, repoShadow, shadowDelta.getModifications(), parentResult);
}
for (ObjectDelta<ShadowType> notificationDelta: notificationDeltas) {
ResourceOperationDescription operationDescription = createSuccessOperationDescription(ctx, repoShadow,
notificationDelta, parentResult);
operationListener.notifySuccess(operationDescription, task, parentResult);
}
if (shadowDelta.isEmpty()) {
return repoShadow;
}
shadowDelta.applyTo(repoShadow);
return repoShadow;
}
private boolean expirePendingOperations(ProvisioningContext ctx, PrismObject<ShadowType> repoShadow, ObjectDelta<ShadowType> shadowDelta, XMLGregorianCalendar now, OperationResult parentResult) throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException {
ShadowType shadowType = repoShadow.asObjectable();
Duration gracePeriod = null;
ResourceConsistencyType consistency = ctx.getResource().getConsistency();
if (consistency != null) {
gracePeriod = consistency.getPendingOperationGracePeriod();
}
boolean atLeastOneOperationRemains = false;
for (PendingOperationType pendingOperation: shadowType.getPendingOperation()) {
ItemPath containerPath = pendingOperation.asPrismContainerValue().getPath();
OperationResultStatusType statusType = pendingOperation.getResultStatus();
XMLGregorianCalendar completionTimestamp = pendingOperation.getCompletionTimestamp();
if (isCompleted(statusType) && isOverGrace(now, gracePeriod, completionTimestamp)) {
LOGGER.trace("Deleting pending operation because it is completed '{}' (and over grace): {}", statusType.value(), pendingOperation);
shadowDelta.addModificationDeleteContainer(new ItemPath(ShadowType.F_PENDING_OPERATION), pendingOperation.clone());
} else {
atLeastOneOperationRemains = true;
}
}
return atLeastOneOperationRemains;
}
private boolean isOverGrace(XMLGregorianCalendar now, Duration gracePeriod, XMLGregorianCalendar completionTimestamp) {
if (gracePeriod == null) {
return true;
}
XMLGregorianCalendar graceExpiration = XmlTypeConverter.addDuration(completionTimestamp, gracePeriod);
return XmlTypeConverter.compare(now, graceExpiration) == DatatypeConstants.GREATER;
}
private boolean isCompleted(OperationResultStatusType statusType) {
return statusType != OperationResultStatusType.IN_PROGRESS && statusType != OperationResultStatusType.UNKNOWN;
}
private List<PendingOperationType> sortOperations(List<PendingOperationType> pendingOperations) {
// Copy to mutable list that is not bound to the prism
List<PendingOperationType> sortedList = new ArrayList<>(pendingOperations.size());
sortedList.addAll(pendingOperations);
Collections.sort(sortedList, (o1,o2) -> XmlTypeConverter.compare(o1.getRequestTimestamp(), o2.getRequestTimestamp()) );
return sortedList;
}
public void applyDefinition(ObjectDelta<ShadowType> delta, ShadowType shadowTypeWhenNoOid,
OperationResult parentResult) throws SchemaException, ObjectNotFoundException,
CommunicationException, ConfigurationException {
PrismObject<ShadowType> shadow = null;
ResourceShadowDiscriminator discriminator = null;
if (delta.isAdd()) {
shadow = delta.getObjectToAdd();
} else if (delta.isModify()) {
if (delta instanceof ShadowDiscriminatorObjectDelta) {
// This one does not have OID, it has to be specially processed
discriminator = ((ShadowDiscriminatorObjectDelta) delta).getDiscriminator();
} else {
String shadowOid = delta.getOid();
if (shadowOid == null) {
if (shadowTypeWhenNoOid == null) {
throw new IllegalArgumentException("No OID in object delta " + delta
+ " and no externally-supplied shadow is present as well.");
}
shadow = shadowTypeWhenNoOid.asPrismObject();
} else {
shadow = repositoryService.getObject(delta.getObjectTypeClass(), shadowOid, null,
parentResult); // TODO consider fetching only when
// really necessary
}
}
} else {
// Delete delta, nothing to do at all
return;
}
ProvisioningContext ctx;
if (shadow == null) {
ctx = ctxFactory.create(discriminator, null, parentResult);
ctx.assertDefinition();
} else {
ctx = ctxFactory.create(shadow, null, parentResult);
ctx.assertDefinition();
}
applyAttributesDefinition(ctx, delta);
}
public void applyDefinition(PrismObject<ShadowType> shadow, OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException {
ProvisioningContext ctx = ctxFactory.create(shadow, null, parentResult);
ctx.assertDefinition();
ctx = applyAttributesDefinition(ctx, shadow);
}
public void setProtectedShadow(PrismObject<ShadowType> shadow, OperationResult parentResult)
throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException {
ProvisioningContext ctx = ctxFactory.create(shadow, null, parentResult);
ctx.assertDefinition();
ProvisioningUtil.setProtectedFlag(ctx, shadow, matchingRuleRegistry);
}
public void applyDefinition(final ObjectQuery query, OperationResult result)
throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException {
ResourceShadowDiscriminator coordinates = ObjectQueryUtil.getCoordinates(query.getFilter());
ProvisioningContext ctx = ctxFactory.create(coordinates, null, result);
ctx.assertDefinition();
applyDefinition(ctx, query);
}
private void applyDefinition(final ProvisioningContext ctx, final ObjectQuery query)
throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException {
if (query == null) {
return;
}
ObjectFilter filter = query.getFilter();
if (filter == null) {
return;
}
final RefinedObjectClassDefinition objectClassDefinition = ctx.getObjectClassDefinition();
final ItemPath attributesPath = new ItemPath(ShadowType.F_ATTRIBUTES);
com.evolveum.midpoint.prism.query.Visitor visitor = subfilter -> {
if (subfilter instanceof PropertyValueFilter) {
PropertyValueFilter<?> valueFilter = (PropertyValueFilter<?>) subfilter;
ItemDefinition definition = valueFilter.getDefinition();
if (definition instanceof ResourceAttributeDefinition) {
return; // already has a resource-related definition
}
if (!attributesPath.equivalent(valueFilter.getParentPath())) {
return;
}
QName attributeName = valueFilter.getElementName();
ResourceAttributeDefinition attributeDefinition = objectClassDefinition.findAttributeDefinition(attributeName);
if (attributeDefinition == null) {
throw new TunnelException(new SchemaException("No definition for attribute "
+ attributeName + " in query " + query));
}
valueFilter.setDefinition(attributeDefinition);
}
};
try {
filter.accept(visitor);
} catch (TunnelException te) {
SchemaException e = (SchemaException) te.getCause();
throw e;
}
}
protected ResourceType getResource(ResourceShadowDiscriminator coords, OperationResult parentResult)
throws ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException {
String resourceOid = coords.getResourceOid();
if (resourceOid == null) {
throw new IllegalArgumentException("No resource OID in " + coords);
}
return resourceManager.getResource(resourceOid, null, parentResult).asObjectable();
}
@SuppressWarnings("rawtypes")
protected PrismObject<ShadowType> handleError(ProvisioningContext ctx, Exception ex,
PrismObject<ShadowType> shadow, FailedOperation op, Collection<? extends ItemDelta> modifications,
boolean doDiscovery, boolean compensate, OperationResult parentResult) throws SchemaException,
GenericFrameworkException, CommunicationException, ObjectNotFoundException,
ObjectAlreadyExistsException, ConfigurationException, SecurityViolationException {
if (parentResult.isUnknown()) {
parentResult.computeStatus();
}
// do not set result in the shadow in case of get operation, it will
// resulted to misleading information
// by get operation we do not modify the result in the shadow, so only
// fetch result in this case needs to be set
if (FailedOperation.GET != op) {
shadow = extendShadow(shadow, parentResult, ctx.getResource(), modifications);
} else {
shadow.asObjectable().setResource(ctx.getResource());
}
ErrorHandler handler = errorHandlerFactory.createErrorHandler(ex);
if (handler == null) {
parentResult.recordFatalError("Error without a handler. Reason: " + ex.getMessage(), ex);
throw new SystemException(ex.getMessage(), ex);
}
LOGGER.debug("Handling provisioning exception {}: {}",
new Object[] { ex.getClass(), ex.getMessage() });
LOGGER.trace("Handling provisioning exception {}: {}\ndoDiscovery={}, compensate={}",
new Object[] { ex.getClass(), ex.getMessage(),
doDiscovery, compensate, ex });
return handler.handleError(shadow.asObjectable(), op, ex, doDiscovery, compensate, ctx.getTask(), parentResult)
.asPrismObject();
}
private PrismObject<ShadowType> extendShadow(PrismObject<ShadowType> shadow, OperationResult shadowResult,
ResourceType resource, Collection<? extends ItemDelta> modifications) throws SchemaException {
ShadowType shadowType = shadow.asObjectable();
shadowType.setResult(shadowResult.createOperationResultType());
shadowType.setResource(resource);
if (modifications != null) {
ObjectDelta<? extends ObjectType> objectDelta = ObjectDelta.createModifyDelta(shadow.getOid(),
modifications, shadowType.getClass(), prismContext);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Storing delta to shadow:\n{}", objectDelta.debugDump());
}
ContainerDelta<ShadowAssociationType> associationDelta = objectDelta.findContainerDelta(ShadowType.F_ASSOCIATION);
if (associationDelta != null) {
normalizeAssociationDeltasBeforeSave(associationDelta.getValuesToAdd());
normalizeAssociationDeltasBeforeSave(associationDelta.getValuesToReplace());
normalizeAssociationDeltasBeforeSave(associationDelta.getValuesToDelete());
}
ObjectDeltaType objectDeltaType = DeltaConvertor.toObjectDeltaType(objectDelta);
shadowType.setObjectChange(objectDeltaType);
}
return shadow;
}
//we need to remove resolved identifiers form the ShadowAssociationType before we save it to the shadow as an unfinished operation.
void normalizeAssociationDeltasBeforeSave(Collection<PrismContainerValue<ShadowAssociationType>> associationContainers) {
if (associationContainers == null) {
return;
}
for (PrismContainerValue<ShadowAssociationType> associationContainer : associationContainers) {
if (associationContainer.contains(ShadowAssociationType.F_IDENTIFIERS) && associationContainer.contains(ShadowAssociationType.F_SHADOW_REF)) {
associationContainer.removeContainer(ShadowAssociationType.F_IDENTIFIERS);
}
}
}
////////////////////////////////////////////////////////////////////////////
// SEARCH
////////////////////////////////////////////////////////////////////////////
public SearchResultMetadata searchObjectsIterative(ObjectQuery query,
Collection<SelectorOptions<GetOperationOptions>> options, final ShadowHandler<ShadowType> handler,
final boolean readFromRepository, Task task, final OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, CommunicationException,
ConfigurationException, SecurityViolationException {
ResourceShadowDiscriminator coordinates = ObjectQueryUtil.getCoordinates(query.getFilter());
final ProvisioningContext ctx = ctxFactory.create(coordinates, task, parentResult);
ctx.setGetOperationOptions(options);
ctx.assertDefinition();
return searchObjectsIterative(ctx, query, options, handler, readFromRepository, parentResult);
}
public SearchResultMetadata searchObjectsIterative(final ProvisioningContext ctx, ObjectQuery query,
Collection<SelectorOptions<GetOperationOptions>> options, final ShadowHandler<ShadowType> handler,
final boolean readFromRepository, final OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, CommunicationException,
ConfigurationException, SecurityViolationException {
applyDefinition(ctx, query);
GetOperationOptions rootOptions = SelectorOptions.findRootOptions(options);
if (ProvisioningUtil.shouldDoRepoSearch(rootOptions)) {
return searchObjectsIterativeRepository(ctx, query, options, handler, parentResult);
}
// We need to record the fetch down here. Now it is certain that we are
// going to fetch from resource
// (we do not have raw/noFetch option)
InternalMonitor.recordShadowFetchOperation();
ObjectQuery attributeQuery = createAttributeQuery(query);
ResultHandler<ShadowType> resultHandler = new ResultHandler<ShadowType>() {
@Override
public boolean handle(PrismObject<ShadowType> resourceShadow) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Found resource object\n{}", resourceShadow.debugDump(1));
}
PrismObject<ShadowType> resultShadow;
try {
// The shadow does not have any kind or intent at this
// point.
// But at least locate the definition using object classes.
ProvisioningContext estimatedShadowCtx = reapplyDefinitions(ctx, resourceShadow);
// Try to find shadow that corresponds to the resource
// object.
if (readFromRepository) {
PrismObject<ShadowType> repoShadow = lookupOrCreateShadowInRepository(
estimatedShadowCtx, resourceShadow, true, parentResult);
// This determines the definitions exactly. How the repo
// shadow should have proper kind/intent
ProvisioningContext shadowCtx = applyAttributesDefinition(ctx, repoShadow);
repoShadow = shadowManager.updateShadow(shadowCtx, resourceShadow, repoShadow,
parentResult);
resultShadow = completeShadow(shadowCtx, resourceShadow, repoShadow, parentResult);
} else {
resultShadow = resourceShadow;
}
// TODO: better error handling
} catch (SchemaException e) {
parentResult.recordFatalError("Schema error: " + e.getMessage(), e);
LOGGER.error("Schema error: {}", e.getMessage(), e);
return false;
} catch (ConfigurationException e) {
parentResult.recordFatalError("Configuration error: " + e.getMessage(), e);
LOGGER.error("Configuration error: {}", e.getMessage(), e);
return false;
} catch (ObjectNotFoundException | ObjectAlreadyExistsException | CommunicationException
| SecurityViolationException | GenericConnectorException e) {
parentResult.recordFatalError(e.getMessage(), e);
LOGGER.error("{}", e.getMessage(), e);
return false;
}
return handler.handle(resultShadow.asObjectable());
}
};
boolean fetchAssociations = SelectorOptions.hasToLoadPath(ShadowType.F_ASSOCIATION, options);
return resouceObjectConverter.searchResourceObjects(ctx, resultHandler, attributeQuery,
fetchAssociations, parentResult);
}
ObjectQuery createAttributeQuery(ObjectQuery query) throws SchemaException {
ObjectFilter filter = null;
if (query != null) {
filter = query.getFilter();
}
ObjectQuery attributeQuery = null;
if (filter instanceof AndFilter) {
List<? extends ObjectFilter> conditions = ((AndFilter) filter).getConditions();
List<ObjectFilter> attributeFilter = createAttributeQueryInternal(conditions);
if (attributeFilter.size() > 1) {
attributeQuery = ObjectQuery.createObjectQuery(AndFilter.createAnd(attributeFilter));
} else if (attributeFilter.size() < 1) {
LOGGER.trace("No attribute filter defined in the query.");
} else if (attributeFilter.size() == 1) {
attributeQuery = ObjectQuery.createObjectQuery(attributeFilter.iterator().next());
}
}
if (query != null && query.getPaging() != null) {
if (attributeQuery == null) {
attributeQuery = new ObjectQuery();
}
attributeQuery.setPaging(query.getPaging());
}
if (query != null && query.isAllowPartialResults()) {
if (attributeQuery == null) {
attributeQuery = new ObjectQuery();
}
attributeQuery.setAllowPartialResults(true);
}
if (InternalsConfig.consistencyChecks && attributeQuery != null
&& attributeQuery.getFilter() != null) {
attributeQuery.getFilter().checkConsistence(true);
}
return attributeQuery;
}
private List<ObjectFilter> createAttributeQueryInternal(List<? extends ObjectFilter> conditions)
throws SchemaException {
List<ObjectFilter> attributeFilter = new ArrayList<>();
for (ObjectFilter f : conditions) {
if (f instanceof EqualFilter) {
ItemPath parentPath = ((EqualFilter) f).getParentPath();
if (parentPath.isEmpty()) {
QName elementName = ((EqualFilter) f).getElementName();
if (QNameUtil.match(ShadowType.F_OBJECT_CLASS, elementName) ||
QNameUtil.match(ShadowType.F_AUXILIARY_OBJECT_CLASS, elementName) ||
QNameUtil.match(ShadowType.F_KIND, elementName) ||
QNameUtil.match(ShadowType.F_INTENT, elementName)) {
continue;
}
throw new SchemaException("Cannot combine on-resource and off-resource properties in a shadow search query. Encountered property " +
((EqualFilter) f).getFullPath());
}
attributeFilter.add(f);
} else if (f instanceof NaryLogicalFilter) {
List<ObjectFilter> subFilters = createAttributeQueryInternal(
((NaryLogicalFilter) f).getConditions());
if (subFilters.size() > 1) {
if (f instanceof OrFilter) {
attributeFilter.add(OrFilter.createOr(subFilters));
} else if (f instanceof AndFilter) {
attributeFilter.add(AndFilter.createAnd(subFilters));
} else {
throw new IllegalArgumentException(
"Could not translate query filter. Unknown type: " + f);
}
} else if (subFilters.size() < 1) {
continue;
} else if (subFilters.size() == 1) {
attributeFilter.add(subFilters.iterator().next());
}
} else if (f instanceof SubstringFilter) {
attributeFilter.add(f);
} else if (f instanceof RefFilter) {
ItemPath parentPath = ((RefFilter)f).getParentPath();
if (parentPath.isEmpty()) {
QName elementName = ((RefFilter) f).getElementName();
if (QNameUtil.match(ShadowType.F_RESOURCE_REF, elementName)) {
continue;
}
}
throw new SchemaException("Cannot combine on-resource and off-resource properties in a shadow search query. Encountered filter " + f);
} else {
throw new SchemaException("Cannot combine on-resource and off-resource properties in a shadow search query. Encountered filter " + f);
}
}
return attributeFilter;
}
private SearchResultMetadata searchObjectsIterativeRepository(final ProvisioningContext ctx,
ObjectQuery query, Collection<SelectorOptions<GetOperationOptions>> options,
final ShadowHandler<ShadowType> shadowHandler, OperationResult parentResult)
throws SchemaException, ConfigurationException, ObjectNotFoundException,
CommunicationException {
com.evolveum.midpoint.schema.ResultHandler<ShadowType> repoHandler = new com.evolveum.midpoint.schema.ResultHandler<ShadowType>() {
@Override
public boolean handle(PrismObject<ShadowType> object, OperationResult parentResult) {
try {
applyAttributesDefinition(ctx, object);
// fixing MID-1640; hoping that the protected object filter uses only identifiers
// (that are stored in repo)
ProvisioningUtil.setProtectedFlag(ctx, object, matchingRuleRegistry);
boolean cont = shadowHandler.handle(object.asObjectable());
parentResult.recordSuccess();
return cont;
} catch (RuntimeException e) {
parentResult.recordFatalError(e);
throw e;
} catch (SchemaException | ConfigurationException | ObjectNotFoundException
| CommunicationException e) {
parentResult.recordFatalError(e);
throw new SystemException(e);
}
}
};
return shadowManager.searchObjectsIterativeRepository(ctx, query, options, repoHandler, parentResult);
}
private PrismObject<ShadowType> lookupOrCreateShadowInRepository(ProvisioningContext ctx,
PrismObject<ShadowType> resourceShadow, boolean unknownIntent, OperationResult parentResult)
throws SchemaException, ConfigurationException, ObjectNotFoundException,
CommunicationException, SecurityViolationException, GenericConnectorException {
PrismObject<ShadowType> repoShadow = shadowManager.lookupShadowInRepository(ctx, resourceShadow,
parentResult);
if (repoShadow == null) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace(
"Shadow object (in repo) corresponding to the resource object (on the resource) was not found. The repo shadow will be created. The resource object:\n{}",
SchemaDebugUtil.prettyPrint(resourceShadow));
}
repoShadow = createShadowInRepository(ctx, resourceShadow, unknownIntent, parentResult);
} else {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Found shadow object in the repository {}",
SchemaDebugUtil.prettyPrint(repoShadow));
}
}
return repoShadow;
}
private PrismObject<ShadowType> createShadowInRepository(ProvisioningContext ctx,
PrismObject<ShadowType> resourceShadow, boolean unknownIntent, OperationResult parentResult)
throws SchemaException, ConfigurationException, ObjectNotFoundException,
CommunicationException, SecurityViolationException, GenericConnectorException {
PrismObject<ShadowType> repoShadow;
PrismObject<ShadowType> conflictingShadow = shadowManager.lookupConflictingShadowBySecondaryIdentifiers(ctx,
resourceShadow, parentResult);
if (conflictingShadow != null) {
applyAttributesDefinition(ctx, conflictingShadow);
conflictingShadow = completeShadow(ctx, resourceShadow, conflictingShadow, parentResult);
Task task = taskManager.createTaskInstance();
ResourceOperationDescription failureDescription = shadowManager
.createResourceFailureDescription(conflictingShadow, ctx.getResource(), parentResult);
changeNotificationDispatcher.notifyFailure(failureDescription, task, parentResult);
shadowManager.deleteConflictedShadowFromRepo(conflictingShadow, parentResult);
}
// The resource object obviously exists on the resource, but appropriate
// shadow does not exist in the
// repository we need to create the shadow to align repo state to the
// reality (resource)
try {
repoShadow = shadowManager.addDiscoveredRepositoryShadow(ctx, resourceShadow, parentResult);
} catch (ObjectAlreadyExistsException e) {
// This should not happen. We haven't supplied an OID so is should
// not conflict
LOGGER.error("Unexpected repository behavior: Object already exists: {}", e.getMessage(), e);
throw new SystemException(
"Unexpected repository behavior: Object already exists: " + e.getMessage(), e);
}
resourceShadow.setOid(repoShadow.getOid());
resourceShadow.asObjectable().setResource(ctx.getResource());
// We have object for which there was no shadow. Which means that
// midPoint haven't known about this shadow
// before. Invoke notifyChange() so the new shadow is properly
// initialized.
ResourceObjectShadowChangeDescription shadowChangeDescription = new ResourceObjectShadowChangeDescription();
shadowChangeDescription.setResource(ctx.getResource().asPrismObject());
shadowChangeDescription.setOldShadow(null);
shadowChangeDescription.setCurrentShadow(resourceShadow);
shadowChangeDescription.setSourceChannel(SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI);
shadowChangeDescription.setUnrelatedChange(true);
Task task = taskManager.createTaskInstance();
notifyResourceObjectChangeListeners(shadowChangeDescription, task, task.getResult());
if (unknownIntent) {
// Intent may have been changed during the notifyChange processing.
// Re-read the shadow if necessary.
repoShadow = shadowManager.fixShadow(ctx, repoShadow, parentResult);
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Final repo shadow (created):\n{}", repoShadow.debugDump());
}
return repoShadow;
}
public Integer countObjects(ObjectQuery query, Task task, final OperationResult result)
throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException,
SecurityViolationException {
ResourceShadowDiscriminator coordinates = ObjectQueryUtil.getCoordinates(query.getFilter());
final ProvisioningContext ctx = ctxFactory.create(coordinates, null, result);
ctx.assertDefinition();
applyDefinition(ctx, query);
RefinedObjectClassDefinition objectClassDef = ctx.getObjectClassDefinition();
ResourceType resourceType = ctx.getResource();
CountObjectsCapabilityType countObjectsCapabilityType = objectClassDef
.getEffectiveCapability(CountObjectsCapabilityType.class);
if (countObjectsCapabilityType == null) {
// Unable to count. Return null which means "I do not know"
result.recordNotApplicableIfUnknown();
return null;
} else {
CountObjectsSimulateType simulate = countObjectsCapabilityType.getSimulate();
if (simulate == null) {
// We have native capability
ConnectorInstance connector = ctx.getConnector(ReadCapabilityType.class, result);
try {
ObjectQuery attributeQuery = createAttributeQuery(query);
int count;
try {
count = connector.count(objectClassDef.getObjectClassDefinition(), attributeQuery,
objectClassDef.getPagedSearches(), ctx, result);
} catch (CommunicationException | GenericFrameworkException | SchemaException
| UnsupportedOperationException e) {
result.recordFatalError(e);
throw e;
}
result.computeStatus();
result.cleanupResult();
return count;
} catch (GenericFrameworkException | UnsupportedOperationException e) {
SystemException ex = new SystemException(
"Couldn't count objects on resource " + resourceType + ": " + e.getMessage(), e);
result.recordFatalError(ex);
throw ex;
}
} else if (simulate == CountObjectsSimulateType.PAGED_SEARCH_ESTIMATE) {
if (!objectClassDef.isPagedSearchEnabled()) {
throw new ConfigurationException(
"Configured count object capability to be simulated using a paged search but paged search capability is not present");
}
final Holder<Integer> countHolder = new Holder<Integer>(0);
final ShadowHandler<ShadowType> handler = new ShadowHandler<ShadowType>() {
@Override
public boolean handle(ShadowType object) {
int count = countHolder.getValue();
count++;
countHolder.setValue(count);
return true;
}
};
query = query.clone();
ObjectPaging paging = ObjectPaging.createEmptyPaging();
paging.setMaxSize(1);
query.setPaging(paging);
Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(
new ItemPath(ShadowType.F_ASSOCIATION),
GetOperationOptions.createRetrieve(RetrieveOption.EXCLUDE));
SearchResultMetadata resultMetadata;
try {
resultMetadata = searchObjectsIterative(query, options, handler, false, task, result);
} catch (SchemaException | ObjectNotFoundException | ConfigurationException
| SecurityViolationException e) {
result.recordFatalError(e);
throw e;
}
result.computeStatus();
result.cleanupResult();
return resultMetadata.getApproxNumberOfAllResults();
} else if (simulate == CountObjectsSimulateType.SEQUENTIAL_SEARCH) {
// traditional way of counting objects (i.e. counting them one
// by one)
final Holder<Integer> countHolder = new Holder<Integer>(0);
final ShadowHandler<ShadowType> handler = new ShadowHandler<ShadowType>() {
@Override
public boolean handle(ShadowType object) {
int count = countHolder.getValue();
count++;
countHolder.setValue(count);
return true;
}
};
Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(
new ItemPath(ShadowType.F_ASSOCIATION),
GetOperationOptions.createRetrieve(RetrieveOption.EXCLUDE));
searchObjectsIterative(query, options, handler, false, task, result);
// TODO: better error handling
result.computeStatus();
result.cleanupResult();
return countHolder.getValue();
} else {
throw new IllegalArgumentException("Unknown count capability simulate type " + simulate);
}
}
}
///////////////////////////////////////////////////////////////////////////
// TODO: maybe split this to a separate class
///////////////////////////////////////////////////////////////////////////
public int synchronize(ResourceShadowDiscriminator shadowCoordinates, PrismProperty<?> lastToken,
Task task, OperationResult parentResult) throws ObjectNotFoundException, CommunicationException,
GenericFrameworkException, SchemaException, ConfigurationException,
SecurityViolationException, ObjectAlreadyExistsException {
InternalMonitor.recordShadowOtherOperation();
final ProvisioningContext ctx = ctxFactory.create(shadowCoordinates, task, parentResult);
List<Change> changes = null;
try {
changes = resouceObjectConverter.fetchChanges(ctx, lastToken, parentResult);
LOGGER.trace("Found {} change(s). Start processing it (them).", changes.size());
int processedChanges = 0;
for (Change change : changes) {
if (change.isTokenOnly()) {
LOGGER.trace("Found token-only change: {}", change);
task.setExtensionProperty(change.getToken());
continue;
}
ObjectClassComplexTypeDefinition changeObjectClassDefinition = change
.getObjectClassDefinition();
ProvisioningContext shadowCtx;
PrismObject<ShadowType> oldShadow = null;
if (changeObjectClassDefinition == null) {
if (change.getObjectDelta() != null && change.getObjectDelta().isDelete()) {
oldShadow = change.getOldShadow();
if (oldShadow == null) {
oldShadow = shadowManager.findOrAddShadowFromChangeGlobalContext(ctx, change,
parentResult);
}
if (oldShadow == null) {
LOGGER.debug(
"No old shadow for delete synchronization event {}, we probably did not know about that object anyway, so well be ignoring this event",
change);
continue;
}
shadowCtx = ctx.spawn(oldShadow);
} else {
throw new SchemaException("No object class definition in change " + change);
}
} else {
shadowCtx = ctx.spawn(changeObjectClassDefinition.getTypeName());
}
processChange(shadowCtx, change, oldShadow, parentResult);
// this is the case,when we want to skip processing of change,
// because the shadow was not created or found to the resource
// object
// it may be caused with the fact, that the object which was
// created in the resource was deleted before the sync run
// such a change should be skipped to process consistent changes
if (change.getOldShadow() == null) {
PrismProperty<?> newToken = change.getToken();
task.setExtensionProperty(newToken);
processedChanges++;
task.setProgress(task.getProgress() + 1); // because
// processedChanges
// are reflected
// into task
// only at task
// run finish
LOGGER.debug(
"Skipping processing change. Can't find appropriate shadow (e.g. the object was deleted on the resource meantime).");
continue;
}
boolean isSuccess = processSynchronization(shadowCtx, change, parentResult);
boolean retryUnhandledError = true;
if (task.getExtension() != null) {
PrismProperty tokenRetryUnhandledErrProperty = task.getExtensionProperty(SchemaConstants.SYNC_TOKEN_RETRY_UNHANDLED);
if (tokenRetryUnhandledErrProperty != null) {
retryUnhandledError = (boolean) tokenRetryUnhandledErrProperty.getRealValue();
}
}
if (!retryUnhandledError || isSuccess) {
// // get updated token from change,
// // create property modification from new token
// // and replace old token with the new one
PrismProperty<?> newToken = change.getToken();
task.setExtensionProperty(newToken);
processedChanges++;
task.setProgress(task.getProgress() + 1); // because
// processedChanges
// are reflected
// into task
// only at task
// run finish
}
}
// also if no changes was detected, update token
if (changes.isEmpty() && lastToken != null) {
LOGGER.trace("No changes to synchronize on " + ctx.getResource());
task.setExtensionProperty(lastToken);
}
task.savePendingModifications(parentResult);
return processedChanges;
} catch (SchemaException ex) {
parentResult.recordFatalError("Schema error: " + ex.getMessage(), ex);
throw ex;
} catch (CommunicationException ex) {
parentResult.recordFatalError("Communication error: " + ex.getMessage(), ex);
throw ex;
} catch (GenericFrameworkException ex) {
parentResult.recordFatalError("Generic error: " + ex.getMessage(), ex);
throw ex;
} catch (ConfigurationException ex) {
parentResult.recordFatalError("Configuration error: " + ex.getMessage(), ex);
throw ex;
} catch (ObjectNotFoundException ex) {
parentResult.recordFatalError("Object not found error: " + ex.getMessage(), ex);
throw ex;
} catch (ObjectAlreadyExistsException ex) {
parentResult.recordFatalError("Already exists error: " + ex.getMessage(), ex);
throw ex;
}
}
@SuppressWarnings("rawtypes")
boolean processSynchronization(ProvisioningContext ctx, Change change, OperationResult parentResult)
throws SchemaException, ObjectNotFoundException, ObjectAlreadyExistsException,
CommunicationException, ConfigurationException {
OperationResult result = parentResult.createSubresult(OP_PROCESS_SYNCHRONIZATION);
boolean successfull = false;
try {
ResourceObjectShadowChangeDescription shadowChangeDescription = createResourceShadowChangeDescription(
change, ctx.getResource(), ctx.getChannel());
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("**PROVISIONING: Created resource object shadow change description {}",
SchemaDebugUtil.prettyPrint(shadowChangeDescription));
}
OperationResult notifyChangeResult = new OperationResult(
ShadowCache.class.getName() + "notifyChange");
notifyChangeResult.addParam("resourceObjectShadowChangeDescription", shadowChangeDescription);
try {
notifyResourceObjectChangeListeners(shadowChangeDescription, ctx.getTask(), notifyChangeResult);
notifyChangeResult.recordSuccess();
} catch (RuntimeException ex) {
// recordFatalError(LOGGER, notifyChangeResult, "Synchronization
// error: " + ex.getMessage(), ex);
saveAccountResult(shadowChangeDescription, change, notifyChangeResult, result);
throw new SystemException("Synchronization error: " + ex.getMessage(), ex);
}
notifyChangeResult.computeStatus("Error in notify change operation.");
if (notifyChangeResult.isSuccess() || notifyChangeResult.isHandledError()) {
deleteShadowFromRepoIfNeeded(change, result);
successfull = true;
// // get updated token from change,
// // create property modification from new token
// // and replace old token with the new one
// PrismProperty<?> newToken = change.getToken();
// task.setExtensionProperty(newToken);
// processedChanges++;
} else {
successfull = false;
saveAccountResult(shadowChangeDescription, change, notifyChangeResult, result);
}
if (result.isUnknown()) {
result.computeStatus();
}
} catch (SchemaException | ObjectNotFoundException | ObjectAlreadyExistsException |
CommunicationException | ConfigurationException | RuntimeException | Error e) {
result.recordFatalError(e);
throw e;
}
return successfull;
}
private void notifyResourceObjectChangeListeners(ResourceObjectShadowChangeDescription change, Task task,
OperationResult parentResult) {
changeNotificationDispatcher.notifyChange(change, task, parentResult);
}
@SuppressWarnings("unchecked")
private ResourceObjectShadowChangeDescription createResourceShadowChangeDescription(
Change change, ResourceType resourceType, String channel) {
ResourceObjectShadowChangeDescription shadowChangeDescription = new ResourceObjectShadowChangeDescription();
shadowChangeDescription.setObjectDelta(change.getObjectDelta());
shadowChangeDescription.setResource(resourceType.asPrismObject());
shadowChangeDescription.setOldShadow(change.getOldShadow());
shadowChangeDescription.setCurrentShadow(change.getCurrentShadow());
if (null == channel) {
shadowChangeDescription
.setSourceChannel(QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_LIVE_SYNC));
} else {
shadowChangeDescription.setSourceChannel(channel);
}
return shadowChangeDescription;
}
@SuppressWarnings("rawtypes")
private void saveAccountResult(ResourceObjectShadowChangeDescription shadowChangeDescription,
Change change, OperationResult notifyChangeResult, OperationResult parentResult)
throws ObjectNotFoundException, SchemaException, ObjectAlreadyExistsException {
Collection<? extends ItemDelta> shadowModification = createShadowResultModification(change,
notifyChangeResult);
String oid = getOidFromChange(change);
// maybe better error handling is needed
try {
ConstraintsChecker.onShadowModifyOperation(shadowModification);
repositoryService.modifyObject(ShadowType.class, oid, shadowModification, parentResult);
} catch (SchemaException ex) {
parentResult.recordPartialError("Couldn't modify object: schema violation: " + ex.getMessage(),
ex);
// throw ex;
} catch (ObjectNotFoundException ex) {
parentResult.recordWarning("Couldn't modify object: object not found: " + ex.getMessage(), ex);
// throw ex;
} catch (ObjectAlreadyExistsException ex) {
parentResult.recordPartialError(
"Couldn't modify object: object already exists: " + ex.getMessage(), ex);
// throw ex;
}
}
private PrismObjectDefinition<ShadowType> getResourceObjectShadowDefinition() {
// if (resourceObjectShadowDefinition == null) {
return prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class);
// }
// return resourceObjectShadowDefinition;
}
@SuppressWarnings("rawtypes")
private Collection<? extends ItemDelta> createShadowResultModification(Change change,
OperationResult shadowResult) {
PrismObjectDefinition<ShadowType> shadowDefinition = getResourceObjectShadowDefinition();
Collection<ItemDelta> modifications = new ArrayList<ItemDelta>();
PropertyDelta resultDelta = PropertyDelta.createModificationReplaceProperty(ShadowType.F_RESULT,
shadowDefinition, shadowResult.createOperationResultType());
modifications.add(resultDelta);
if (change.getObjectDelta() != null && change.getObjectDelta().getChangeType() == ChangeType.DELETE) {
PropertyDelta failedOperationTypeDelta = PropertyDelta.createModificationReplaceProperty(
ShadowType.F_FAILED_OPERATION_TYPE, shadowDefinition, FailedOperationTypeType.DELETE);
modifications.add(failedOperationTypeDelta);
}
return modifications;
}
private String getOidFromChange(Change change) {
String shadowOid = null;
if (change.getObjectDelta() != null && change.getObjectDelta().getOid() != null) {
shadowOid = change.getObjectDelta().getOid();
} else {
if (change.getCurrentShadow().getOid() != null) {
shadowOid = change.getCurrentShadow().getOid();
} else {
if (change.getOldShadow().getOid() != null) {
shadowOid = change.getOldShadow().getOid();
} else {
throw new IllegalArgumentException("No oid value defined for the object to synchronize.");
}
}
}
return shadowOid;
}
private void deleteShadowFromRepoIfNeeded(Change change, OperationResult parentResult)
throws ObjectNotFoundException {
if (change.getObjectDelta() != null && change.getObjectDelta().getChangeType() == ChangeType.DELETE
&& change.getOldShadow() != null) {
LOGGER.trace("Deleting detected shadow object form repository.");
try {
repositoryService.deleteObject(ShadowType.class, change.getOldShadow().getOid(),
parentResult);
LOGGER.debug("Shadow object successfully deleted form repository.");
} catch (ObjectNotFoundException ex) {
// What we want to delete is already deleted. Not a big problem.
LOGGER.debug("Shadow object {} already deleted from repository ({})", change.getOldShadow(),
ex);
parentResult.recordHandledError(
"Shadow object " + change.getOldShadow() + " already deleted from repository", ex);
}
}
}
void processChange(ProvisioningContext ctx, Change change, PrismObject<ShadowType> oldShadow,
OperationResult parentResult) throws SchemaException, CommunicationException,
ConfigurationException, SecurityViolationException, ObjectNotFoundException,
GenericConnectorException, ObjectAlreadyExistsException {
if (oldShadow == null) {
oldShadow = shadowManager.findOrAddShadowFromChange(ctx, change, parentResult);
}
if (oldShadow != null) {
applyAttributesDefinition(ctx, oldShadow);
LOGGER.trace("Old shadow: {}", oldShadow);
// skip setting other attribute when shadow is null
if (oldShadow == null) {
change.setOldShadow(null);
return;
}
ProvisioningUtil.setProtectedFlag(ctx, oldShadow, matchingRuleRegistry);
change.setOldShadow(oldShadow);
if (change.getCurrentShadow() != null) {
PrismObject<ShadowType> currentShadow = completeShadow(ctx, change.getCurrentShadow(),
oldShadow, parentResult);
change.setCurrentShadow(currentShadow);
shadowManager.updateShadow(ctx, currentShadow, oldShadow, parentResult);
}
// FIXME: hack. the object delta must have oid specified.
if (change.getObjectDelta() != null && change.getObjectDelta().getOid() == null) {
// if (LOGGER.isTraceEnabled()) {
// LOGGER.trace("No OID present, assuming delta of type DELETE;
// change = {}\nobjectDelta: {}", change,
// change.getObjectDelta().debugDump());
// }
// ObjectDelta<ShadowType> objDelta = new
// ObjectDelta<ShadowType>(ShadowType.class, ChangeType.DELETE,
// prismContext);
// change.setObjectDelta(objDelta);
change.getObjectDelta().setOid(oldShadow.getOid());
}
} else {
LOGGER.debug(
"No old shadow for synchronization event {}, the shadow must be gone in the meantime (this is probably harmless)",
change);
}
}
public PrismProperty<?> fetchCurrentToken(ResourceShadowDiscriminator shadowCoordinates,
OperationResult parentResult) throws ObjectNotFoundException, CommunicationException,
SchemaException, ConfigurationException {
Validate.notNull(parentResult, "Operation result must not be null.");
InternalMonitor.recordShadowOtherOperation();
ProvisioningContext ctx = ctxFactory.create(shadowCoordinates, null, parentResult);
LOGGER.trace("Getting last token");
PrismProperty<?> lastToken = null;
try {
lastToken = resouceObjectConverter.fetchCurrentToken(ctx, parentResult);
} catch (CommunicationException | ConfigurationException e) {
parentResult.recordFatalError(e.getMessage(), e);
throw e;
}
LOGGER.trace("Got last token: {}", SchemaDebugUtil.prettyPrint(lastToken));
parentResult.recordSuccess();
return lastToken;
}
private void applyAttributesDefinition(ProvisioningContext ctx, ObjectDelta<ShadowType> delta)
throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
if (delta.isAdd()) {
applyAttributesDefinition(ctx, delta.getObjectToAdd());
} else if (delta.isModify()) {
for (ItemDelta<?, ?> itemDelta : delta.getModifications()) {
if (SchemaConstants.PATH_ATTRIBUTES.equivalent(itemDelta.getParentPath())) {
applyAttributeDefinition(ctx, delta, itemDelta);
} else if (SchemaConstants.PATH_ATTRIBUTES.equivalent(itemDelta.getPath())) {
if (itemDelta.isAdd()) {
for (PrismValue value : itemDelta.getValuesToAdd()) {
applyAttributeDefinition(ctx, value);
}
}
if (itemDelta.isReplace()) {
for (PrismValue value : itemDelta.getValuesToReplace()) {
applyAttributeDefinition(ctx, value);
}
}
}
}
}
}
// value should be a value of attributes container
private void applyAttributeDefinition(ProvisioningContext ctx, PrismValue value)
throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
if (!(value instanceof PrismContainerValue)) {
return; // should never occur
}
PrismContainerValue<ShadowAttributesType> pcv = (PrismContainerValue<ShadowAttributesType>) value;
for (Item item : pcv.getItems()) {
ItemDefinition itemDef = item.getDefinition();
if (itemDef == null || !(itemDef instanceof ResourceAttributeDefinition)) {
QName attributeName = item.getElementName();
ResourceAttributeDefinition attributeDefinition = ctx.getObjectClassDefinition()
.findAttributeDefinition(attributeName);
if (attributeDefinition == null) {
throw new SchemaException("No definition for attribute " + attributeName);
}
if (itemDef != null) {
// We are going to rewrite the definition anyway. Let's just
// do some basic checks first
if (!QNameUtil.match(itemDef.getTypeName(), attributeDefinition.getTypeName())) {
throw new SchemaException("The value of type " + itemDef.getTypeName()
+ " cannot be applied to attribute " + attributeName + " which is of type "
+ attributeDefinition.getTypeName());
}
}
item.applyDefinition(attributeDefinition);
}
}
}
private <V extends PrismValue, D extends ItemDefinition> void applyAttributeDefinition(
ProvisioningContext ctx, ObjectDelta<ShadowType> delta, ItemDelta<V, D> itemDelta)
throws SchemaException, ConfigurationException, ObjectNotFoundException,
CommunicationException {
if (!SchemaConstants.PATH_ATTRIBUTES.equivalent(itemDelta.getParentPath())) { // just
// to
// be
// sure
return;
}
D itemDef = itemDelta.getDefinition();
if (itemDef == null || !(itemDef instanceof ResourceAttributeDefinition)) {
QName attributeName = itemDelta.getElementName();
ResourceAttributeDefinition attributeDefinition = ctx.getObjectClassDefinition()
.findAttributeDefinition(attributeName);
if (attributeDefinition == null) {
throw new SchemaException(
"No definition for attribute " + attributeName + " in object delta " + delta);
}
if (itemDef != null) {
// We are going to rewrite the definition anyway. Let's just do
// some basic checks first
if (!QNameUtil.match(itemDef.getTypeName(), attributeDefinition.getTypeName())) {
throw new SchemaException("The value of type " + itemDef.getTypeName()
+ " cannot be applied to attribute " + attributeName + " which is of type "
+ attributeDefinition.getTypeName());
}
}
itemDelta.applyDefinition((D) attributeDefinition);
}
}
private ProvisioningContext applyAttributesDefinition(ProvisioningContext ctx,
PrismObject<ShadowType> shadow) throws SchemaException, ConfigurationException,
ObjectNotFoundException, CommunicationException {
ProvisioningContext subctx = ctx.spawn(shadow);
RefinedObjectClassDefinition objectClassDefinition = subctx.getObjectClassDefinition();
PrismContainer<ShadowAttributesType> attributesContainer = shadow
.findContainer(ShadowType.F_ATTRIBUTES);
if (attributesContainer != null) {
if (attributesContainer instanceof ResourceAttributeContainer) {
if (attributesContainer.getDefinition() == null) {
attributesContainer
.applyDefinition(objectClassDefinition.toResourceAttributeContainerDefinition());
}
} else {
try {
// We need to convert <attributes> to
// ResourceAttributeContainer
ResourceAttributeContainer convertedContainer = ResourceAttributeContainer
.convertFromContainer(attributesContainer, objectClassDefinition);
shadow.getValue().replace(attributesContainer, convertedContainer);
} catch (SchemaException e) {
throw new SchemaException(e.getMessage() + " in " + shadow, e);
}
}
}
// We also need to replace the entire object definition to inject
// correct object class definition here
// If we don't do this then the patch (delta.applyTo) will not work
// correctly because it will not be able to
// create the attribute container if needed.
PrismObjectDefinition<ShadowType> objectDefinition = shadow.getDefinition();
PrismContainerDefinition<ShadowAttributesType> origAttrContainerDef = objectDefinition
.findContainerDefinition(ShadowType.F_ATTRIBUTES);
if (origAttrContainerDef == null
|| !(origAttrContainerDef instanceof ResourceAttributeContainerDefinition)) {
PrismObjectDefinition<ShadowType> clonedDefinition = objectDefinition.cloneWithReplacedDefinition(
ShadowType.F_ATTRIBUTES, objectClassDefinition.toResourceAttributeContainerDefinition());
shadow.setDefinition(clonedDefinition);
}
return subctx;
}
/**
* Reapplies definition to the shadow if needed. The definition needs to be
* reapplied e.g. if the shadow has auxiliary object classes, it if subclass
* of the object class that was originally requested, etc.
*/
private ProvisioningContext reapplyDefinitions(ProvisioningContext ctx,
PrismObject<ShadowType> rawResourceShadow) throws SchemaException, ConfigurationException,
ObjectNotFoundException, CommunicationException {
ShadowType rawResourceShadowType = rawResourceShadow.asObjectable();
QName objectClassQName = rawResourceShadowType.getObjectClass();
List<QName> auxiliaryObjectClassQNames = rawResourceShadowType.getAuxiliaryObjectClass();
if (auxiliaryObjectClassQNames.isEmpty()
&& objectClassQName.equals(ctx.getObjectClassDefinition().getTypeName())) {
// shortcut, no need to reapply anything
return ctx;
}
ProvisioningContext shadowCtx = ctx.spawn(rawResourceShadow);
shadowCtx.assertDefinition();
RefinedObjectClassDefinition shadowDef = shadowCtx.getObjectClassDefinition();
ResourceAttributeContainer attributesContainer = ShadowUtil.getAttributesContainer(rawResourceShadow);
attributesContainer.applyDefinition(shadowDef.toResourceAttributeContainerDefinition());
return shadowCtx;
}
/**
* Make sure that the shadow is complete, e.g. that all the mandatory fields
* are filled (e.g name, resourceRef, ...) Also transforms the shadow with
* respect to simulated capabilities.
*/
private PrismObject<ShadowType> completeShadow(ProvisioningContext ctx,
PrismObject<ShadowType> resourceShadow, PrismObject<ShadowType> repoShadow,
OperationResult parentResult)
throws SchemaException, ConfigurationException, ObjectNotFoundException,
CommunicationException, SecurityViolationException, GenericConnectorException {
PrismObject<ShadowType> resultShadow = repoShadow.clone();
// The real definition may be different than that of repo shadow (e.g.
// different auxiliary object classes).
resultShadow.applyDefinition(ctx.getObjectClassDefinition().getObjectDefinition(), true);
assert resultShadow.getPrismContext() != null : "No prism context in resultShadow";
ResourceAttributeContainer resourceAttributesContainer = ShadowUtil
.getAttributesContainer(resourceShadow);
ShadowType resultShadowType = resultShadow.asObjectable();
ShadowType repoShadowType = repoShadow.asObjectable();
ShadowType resourceShadowType = resourceShadow.asObjectable();
Collection<QName> auxObjectClassQNames = new ArrayList<>();
// Always take auxiliary object classes from the resource. Unlike
// structural object classes
// the auxiliary object classes may change.
resultShadow.removeProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS);
PrismProperty<QName> resourceAuxOcProp = resourceShadow
.findProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS);
if (resourceAuxOcProp != null) {
PrismProperty<QName> resultAuxOcProp = resultShadow
.findOrCreateProperty(ShadowType.F_AUXILIARY_OBJECT_CLASS);
resultAuxOcProp.addAll(PrismPropertyValue.cloneCollection(resourceAuxOcProp.getValues()));
auxObjectClassQNames.addAll(resultAuxOcProp.getRealValues());
}
resultShadowType.setName(new PolyStringType(ShadowUtil.determineShadowName(resourceShadow)));
if (resultShadowType.getObjectClass() == null) {
resultShadowType.setObjectClass(resourceAttributesContainer.getDefinition().getTypeName());
}
if (resultShadowType.getResource() == null) {
resultShadowType.setResourceRef(ObjectTypeUtil.createObjectRef(ctx.getResource()));
}
// Attributes
resultShadow.removeContainer(ShadowType.F_ATTRIBUTES);
ResourceAttributeContainer resultAttibutes = resourceAttributesContainer.clone();
accessChecker.filterGetAttributes(resultAttibutes, ctx.computeCompositeObjectClassDefinition(auxObjectClassQNames), parentResult);
resultShadow.add(resultAttibutes);
resultShadowType.setIgnored(resourceShadowType.isIgnored());
resultShadowType.setActivation(resourceShadowType.getActivation());
ShadowType resultAccountShadow = resultShadow.asObjectable();
ShadowType resourceAccountShadow = resourceShadow.asObjectable();
// Credentials
resultAccountShadow.setCredentials(resourceAccountShadow.getCredentials());
transplantPasswordMetadata(repoShadowType, resultAccountShadow);
// protected
ProvisioningUtil.setProtectedFlag(ctx, resultShadow, matchingRuleRegistry);
// exists, dead
resultShadowType.setExists(resourceShadowType.isExists());
// Activation
ActivationType resultActivationType = resultShadowType.getActivation();
ActivationType repoActivation = repoShadowType.getActivation();
if (repoActivation != null) {
if (resultActivationType == null) {
resultActivationType = new ActivationType();
resultShadowType.setActivation(resultActivationType);
}
resultActivationType.setId(repoActivation.getId());
// .. but we want metadata from repo
resultActivationType.setDisableReason(repoActivation.getDisableReason());
resultActivationType.setEnableTimestamp(repoActivation.getEnableTimestamp());
resultActivationType.setDisableTimestamp(repoActivation.getDisableTimestamp());
resultActivationType.setArchiveTimestamp(repoActivation.getArchiveTimestamp());
resultActivationType.setValidityChangeTimestamp(repoActivation.getValidityChangeTimestamp());
}
// Associations
PrismContainer<ShadowAssociationType> resourceAssociationContainer = resourceShadow
.findContainer(ShadowType.F_ASSOCIATION);
if (resourceAssociationContainer != null) {
PrismContainer<ShadowAssociationType> associationContainer = resourceAssociationContainer.clone();
resultShadow.addReplaceExisting(associationContainer);
if (associationContainer != null) {
for (PrismContainerValue<ShadowAssociationType> associationCVal : associationContainer
.getValues()) {
ResourceAttributeContainer identifierContainer = ShadowUtil
.getAttributesContainer(associationCVal, ShadowAssociationType.F_IDENTIFIERS);
Collection<ResourceAttribute<?>> entitlementIdentifiers = identifierContainer
.getAttributes();
if (entitlementIdentifiers == null || entitlementIdentifiers.isEmpty()) {
throw new IllegalStateException(
"No entitlement identifiers present for association " + associationCVal + " " + ctx.getDesc());
}
ShadowAssociationType shadowAssociationType = associationCVal.asContainerable();
QName associationName = shadowAssociationType.getName();
RefinedAssociationDefinition rEntitlementAssociation = ctx.getObjectClassDefinition()
.findEntitlementAssociationDefinition(associationName);
if (rEntitlementAssociation == null) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Entitlement association with name {} couldn't be found in {} {}\nresource shadow:\n{}\nrepo shadow:\n{}",
new Object[]{ associationName, ctx.getObjectClassDefinition(), ctx.getDesc(),
resourceShadow.debugDump(1), repoShadow==null?null:repoShadow.debugDump(1)});
LOGGER.trace("Full refined definition: {}", ctx.getObjectClassDefinition().debugDump());
}
throw new SchemaException("Entitlement association with name " + associationName
+ " couldn't be found in " + ctx.getObjectClassDefinition() + " " + ctx.getDesc() + ", with using shadow coordinates " + ctx.isUseRefinedDefinition());
}
for (String intent : rEntitlementAssociation.getIntents()) {
ProvisioningContext ctxEntitlement = ctx.spawn(ShadowKindType.ENTITLEMENT, intent);
PrismObject<ShadowType> entitlementRepoShadow;
PrismObject<ShadowType> entitlementShadow = (PrismObject<ShadowType>) identifierContainer
.getUserData(ResourceObjectConverter.FULL_SHADOW_KEY);
if (entitlementShadow == null) {
try {
entitlementRepoShadow = shadowManager.lookupShadowInRepository(ctxEntitlement,
identifierContainer, parentResult);
if (entitlementRepoShadow == null) {
entitlementShadow = resouceObjectConverter.locateResourceObject(
ctxEntitlement, entitlementIdentifiers, parentResult);
// Try to look up repo shadow again, this
// time with full resource shadow. When we
// have searched before we might
// have only some identifiers. The shadow
// might still be there, but it may be
// renamed
entitlementRepoShadow = shadowManager.lookupShadowInRepository(
ctxEntitlement, entitlementShadow, parentResult);
if (entitlementRepoShadow == null) {
entitlementRepoShadow = createShadowInRepository(ctxEntitlement,
entitlementShadow, false, parentResult);
}
}
} catch (ObjectNotFoundException e) {
// The entitlement to which we point is not
// there.
// Simply ignore this association value.
parentResult.muteLastSubresultError();
LOGGER.warn(
"The entitlement identified by {} referenced from {} does not exist. Skipping.",
new Object[] { associationCVal, resourceShadow });
continue;
} catch (SchemaException e) {
// The entitlement to which we point is not bad.
// Simply ignore this association value.
parentResult.muteLastSubresultError();
LOGGER.warn(
"The entitlement identified by {} referenced from {} violates the schema. Skipping. Original error: {}",
new Object[] { associationCVal, resourceShadow, e.getMessage(), e });
continue;
}
} else {
entitlementRepoShadow = lookupOrCreateShadowInRepository(ctxEntitlement,
entitlementShadow, false, parentResult);
}
ObjectReferenceType shadowRefType = new ObjectReferenceType();
shadowRefType.setOid(entitlementRepoShadow.getOid());
shadowRefType.setType(ShadowType.COMPLEX_TYPE);
shadowAssociationType.setShadowRef(shadowRefType);
}
}
}
}
resultShadowType.setCachingMetadata(resourceShadowType.getCachingMetadata());
// Sanity asserts to catch some exotic bugs
PolyStringType resultName = resultShadow.asObjectable().getName();
assert resultName != null : "No name generated in " + resultShadow;
assert !StringUtils.isEmpty(resultName.getOrig()) : "No name (orig) in " + resultShadow;
assert !StringUtils.isEmpty(resultName.getNorm()) : "No name (norm) in " + resultShadow;
return resultShadow;
}
private void transplantPasswordMetadata(ShadowType repoShadowType, ShadowType resultAccountShadow) {
CredentialsType repoCreds = repoShadowType.getCredentials();
if (repoCreds == null) {
return;
}
PasswordType repoPassword = repoCreds.getPassword();
if (repoPassword == null) {
return;
}
MetadataType repoMetadata = repoPassword.getMetadata();
if (repoMetadata == null) {
return;
}
CredentialsType resultCreds = resultAccountShadow.getCredentials();
if (resultCreds == null) {
resultCreds = new CredentialsType();
resultAccountShadow.setCredentials(resultCreds);
}
PasswordType resultPassword = resultCreds.getPassword();
if (resultPassword == null) {
resultPassword = new PasswordType();
resultCreds.setPassword(resultPassword);
}
MetadataType resultMetadata = resultPassword.getMetadata();
if (resultMetadata == null) {
resultMetadata = repoMetadata.clone();
resultPassword.setMetadata(resultMetadata);
}
}
// ENTITLEMENTS
/**
* Makes sure that all the entitlements have identifiers in them so this is
* usable by the ResourceObjectConverter.
*/
private void preprocessEntitlements(final ProvisioningContext ctx, final PrismObject<ShadowType> shadow,
final OperationResult result) throws SchemaException, ObjectNotFoundException,
ConfigurationException, CommunicationException {
Visitor visitor = new Visitor() {
@Override
public void visit(Visitable visitable) {
try {
preprocessEntitlement(ctx, (PrismContainerValue<ShadowAssociationType>) visitable,
shadow.toString(), result);
} catch (SchemaException | ObjectNotFoundException | ConfigurationException
| CommunicationException e) {
throw new TunnelException(e);
}
}
};
try {
shadow.accept(visitor, new ItemPath(new NameItemPathSegment(ShadowType.F_ASSOCIATION),
IdItemPathSegment.WILDCARD), false);
} catch (TunnelException e) {
Throwable cause = e.getCause();
if (cause instanceof SchemaException) {
throw (SchemaException) cause;
} else if (cause instanceof ObjectNotFoundException) {
throw (ObjectNotFoundException) cause;
} else if (cause instanceof ConfigurationException) {
throw (ConfigurationException) cause;
} else if (cause instanceof CommunicationException) {
throw (CommunicationException) cause;
} else {
throw new SystemException("Unexpected exception " + cause, cause);
}
}
}
/**
* Makes sure that all the entitlements have identifiers in them so this is
* usable by the ResourceObjectConverter.
*/
private void preprocessEntitlements(final ProvisioningContext ctx,
Collection<? extends ItemDelta> modifications, final String desc, final OperationResult result)
throws SchemaException, ObjectNotFoundException, ConfigurationException,
CommunicationException {
Visitor visitor = new Visitor() {
@Override
public void visit(Visitable visitable) {
try {
preprocessEntitlement(ctx, (PrismContainerValue<ShadowAssociationType>) visitable, desc,
result);
} catch (SchemaException | ObjectNotFoundException | ConfigurationException
| CommunicationException e) {
throw new TunnelException(e);
}
}
};
try {
ItemDelta.accept(modifications, visitor, new ItemPath(
new NameItemPathSegment(ShadowType.F_ASSOCIATION), IdItemPathSegment.WILDCARD), false);
} catch (TunnelException e) {
Throwable cause = e.getCause();
if (cause instanceof SchemaException) {
throw (SchemaException) cause;
} else if (cause instanceof ObjectNotFoundException) {
throw (ObjectNotFoundException) cause;
} else if (cause instanceof ConfigurationException) {
throw (ConfigurationException) cause;
} else if (cause instanceof CommunicationException) {
throw (CommunicationException) cause;
} else {
throw new SystemException("Unexpected exception " + cause, cause);
}
}
}
private void preprocessEntitlement(ProvisioningContext ctx,
PrismContainerValue<ShadowAssociationType> association, String desc, OperationResult result)
throws SchemaException, ObjectNotFoundException, ConfigurationException,
CommunicationException {
PrismContainer<Containerable> identifiersContainer = association
.findContainer(ShadowAssociationType.F_IDENTIFIERS);
if (identifiersContainer != null && !identifiersContainer.isEmpty()) {
// We already have identifiers here
return;
}
ShadowAssociationType associationType = association.asContainerable();
if (associationType.getShadowRef() == null
|| StringUtils.isEmpty(associationType.getShadowRef().getOid())) {
throw new SchemaException(
"No identifiers and no OID specified in entitlements association " + association);
}
PrismObject<ShadowType> repoShadow;
try {
repoShadow = repositoryService.getObject(ShadowType.class,
associationType.getShadowRef().getOid(), null, result);
} catch (ObjectNotFoundException e) {
throw new ObjectNotFoundException(e.getMessage()
+ " while resolving entitlement association OID in " + association + " in " + desc, e);
}
applyAttributesDefinition(ctx, repoShadow);
transplantIdentifiers(association, repoShadow);
}
private void transplantIdentifiers(PrismContainerValue<ShadowAssociationType> association,
PrismObject<ShadowType> repoShadow) throws SchemaException {
PrismContainer<Containerable> identifiersContainer = association
.findContainer(ShadowAssociationType.F_IDENTIFIERS);
if (identifiersContainer == null) {
ResourceAttributeContainer origContainer = ShadowUtil.getAttributesContainer(repoShadow);
identifiersContainer = new ResourceAttributeContainer(ShadowAssociationType.F_IDENTIFIERS,
origContainer.getDefinition(), prismContext);
association.add(identifiersContainer);
}
Collection<ResourceAttribute<?>> identifiers = ShadowUtil.getPrimaryIdentifiers(repoShadow);
for (ResourceAttribute<?> identifier : identifiers) {
identifiersContainer.add(identifier.clone());
}
Collection<ResourceAttribute<?>> secondaryIdentifiers = ShadowUtil
.getSecondaryIdentifiers(repoShadow);
for (ResourceAttribute<?> identifier : secondaryIdentifiers) {
identifiersContainer.add(identifier.clone());
}
}
}