/*
* 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.model.impl.lens.projector;
import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.internals.InternalsConfig;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
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 org.springframework.stereotype.Component;
import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition;
import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision;
import com.evolveum.midpoint.model.common.SystemObjectCache;
import com.evolveum.midpoint.model.impl.controller.ModelUtils;
import com.evolveum.midpoint.model.impl.lens.LensContext;
import com.evolveum.midpoint.model.impl.lens.LensElementContext;
import com.evolveum.midpoint.model.impl.lens.LensFocusContext;
import com.evolveum.midpoint.model.impl.lens.LensObjectDeltaOperation;
import com.evolveum.midpoint.model.impl.lens.LensProjectionContext;
import com.evolveum.midpoint.model.impl.lens.LensUtil;
import com.evolveum.midpoint.model.impl.lens.SynchronizationIntent;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismReference;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.delta.ChangeType;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.prism.delta.PropertyDelta;
import com.evolveum.midpoint.prism.delta.ReferenceDelta;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.provisioning.api.ProvisioningService;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.PointInTimeType;
import com.evolveum.midpoint.schema.ResourceShadowDiscriminator;
import com.evolveum.midpoint.schema.RetrieveOption;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ExceptionUtil;
import com.evolveum.midpoint.schema.util.FocusTypeUtil;
import com.evolveum.midpoint.schema.util.MiscSchemaUtil;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ExpressionEvaluationException;
import com.evolveum.midpoint.util.exception.ObjectAlreadyExistsException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.PolicyViolationException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
/**
* Context loader loads the missing parts of the context. The context enters the projector with just the minimum information.
* Context loader gets missing data such as accounts. It gets them from the repository or provisioning as necessary. It follows
* the account links in user (accountRef) and user deltas.
*
* @author Radovan Semancik
*
*/
@Component
public class ContextLoader {
@Autowired(required = true)
@Qualifier("cacheRepositoryService")
private transient RepositoryService cacheRepositoryService;
@Autowired(required = true)
private SystemObjectCache systemObjectCache;
@Autowired(required = true)
private ProvisioningService provisioningService;
@Autowired(required = true)
private PrismContext prismContext;
private static final Trace LOGGER = TraceManager.getTrace(ContextLoader.class);
public <F extends ObjectType> void load(LensContext<F> context, String activityDescription,
Task task, OperationResult result)
throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException,
SecurityViolationException, PolicyViolationException {
context.checkAbortRequested();
context.recompute();
for (LensProjectionContext projectionContext: context.getProjectionContexts()) {
preprocessProjectionContext(context, projectionContext, task, result);
}
if (consistencyChecks) context.checkConsistence();
determineFocusContext((LensContext<? extends FocusType>)context, result);
LensFocusContext<F> focusContext = context.getFocusContext();
if (focusContext != null) {
loadObjectCurrent(context, result);
context.recomputeFocus();
loadFromSystemConfig(context, result);
if (FocusType.class.isAssignableFrom(context.getFocusClass())) {
// this also removes the accountRef deltas
loadLinkRefs((LensContext<? extends FocusType>)context, task, result);
LOGGER.trace("loadLinkRefs done");
}
// Some cleanup
if (focusContext.getPrimaryDelta() != null && focusContext.getPrimaryDelta().isModify() && focusContext.getPrimaryDelta().isEmpty()) {
focusContext.setPrimaryDelta(null);
}
for (LensProjectionContext projectionContext: context.getProjectionContexts()) {
if (projectionContext.getSynchronizationIntent() != null) {
// Accounts with explicitly set intent are never rotten. These are explicitly requested actions
// if they fail then they really should fail.
projectionContext.setFresh(true);
}
}
setPrimaryDeltaOldValue(focusContext);
} else {
// Projection contexts are not rotten in this case. There is no focus so there is no way to refresh them.
for (LensProjectionContext projectionContext: context.getProjectionContexts()) {
projectionContext.setFresh(true);
}
}
removeRottenContexts(context);
if (consistencyChecks) context.checkConsistence();
for (LensProjectionContext projectionContext: context.getProjectionContexts()) {
context.checkAbortRequested();
finishLoadOfProjectionContext(context, projectionContext, task, result);
}
if (consistencyChecks) context.checkConsistence();
context.recompute();
if (consistencyChecks) {
fullCheckConsistence(context);
}
LensUtil.traceContext(LOGGER, activityDescription, "after load", false, context, false);
}
/**
* Removes projection contexts that are not fresh.
* These are usually artifacts left after the context reload. E.g. an account that used to be linked to a user before
* but was removed in the meantime.
*/
private <F extends ObjectType> void removeRottenContexts(LensContext<F> context) {
Iterator<LensProjectionContext> projectionIterator = context.getProjectionContextsIterator();
while (projectionIterator.hasNext()) {
LensProjectionContext projectionContext = projectionIterator.next();
if (projectionContext.getPrimaryDelta() != null && !projectionContext.getPrimaryDelta().isEmpty()) {
// We must never remove contexts with primary delta. Even though it fails later on.
// What the user wishes should be done (or at least attempted) regardless of the consequences.
// Vox populi vox dei
continue;
}
if (projectionContext.getWave() >= context.getExecutionWave()) {
// We must not remove context from this and later execution waves. These haven't had the
// chance to be executed yet
continue;
}
ResourceShadowDiscriminator discr = projectionContext.getResourceShadowDiscriminator();
if (discr != null && discr.getOrder() > 0) {
// HACK never rot higher-order context. TODO: check if lower-order context is rotten, the also rot this one
continue;
}
if (!projectionContext.isFresh()) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Removing rotten context {}", projectionContext.getHumanReadableName());
}
if (projectionContext.isToBeArchived()) {
context.getHistoricResourceObjects().add(projectionContext.getResourceShadowDiscriminator());
}
List<LensObjectDeltaOperation<ShadowType>> executedDeltas = projectionContext.getExecutedDeltas();
context.getRottenExecutedDeltas().addAll(executedDeltas);
projectionIterator.remove();
}
}
}
/**
* Make sure that the projection context is loaded as approppriate.
*/
public <F extends ObjectType> void makeSureProjectionIsLoaded(LensContext<F> context,
LensProjectionContext projectionContext, Task task, OperationResult result) throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException {
preprocessProjectionContext(context, projectionContext, task, result);
finishLoadOfProjectionContext(context, projectionContext, task, result);
}
/**
* Make sure that the context is OK and consistent. It means that is has a resource, it has correctly processed
* discriminator, etc.
*/
private <F extends ObjectType> void preprocessProjectionContext(LensContext<F> context,
LensProjectionContext projectionContext, Task task, OperationResult result)
throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException {
if (!ShadowType.class.isAssignableFrom(projectionContext.getObjectTypeClass())) {
return;
}
String resourceOid = null;
boolean isThombstone = false;
ShadowKindType kind = ShadowKindType.ACCOUNT;
String intent = null;
int order = 0;
ResourceShadowDiscriminator rsd = projectionContext.getResourceShadowDiscriminator();
if (rsd != null) {
resourceOid = rsd.getResourceOid();
isThombstone = rsd.isThombstone();
kind = rsd.getKind();
intent = rsd.getIntent();
order = rsd.getOrder();
}
if (resourceOid == null && projectionContext.getObjectCurrent() != null) {
resourceOid = ShadowUtil.getResourceOid((ShadowType) projectionContext.getObjectCurrent().asObjectable());
}
if (resourceOid == null && projectionContext.getObjectNew() != null) {
resourceOid = ShadowUtil.getResourceOid((ShadowType) projectionContext.getObjectNew().asObjectable());
}
// We still may not have resource OID here. E.g. in case of the delete when the account is not loaded yet. It is
// perhaps safe to skip this. It will be sorted out later.
if (resourceOid != null) {
if (intent == null && projectionContext.getObjectNew() != null) {
ShadowType shadowNewType = projectionContext.getObjectNew().asObjectable();
kind = ShadowUtil.getKind(shadowNewType);
intent = ShadowUtil.getIntent(shadowNewType);
}
ResourceType resource = projectionContext.getResource();
if (resource == null) {
resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result);
projectionContext.setResource(resource);
}
String refinedIntent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext);
rsd = new ResourceShadowDiscriminator(resourceOid, kind, refinedIntent, isThombstone);
rsd.setOrder(order);
projectionContext.setResourceShadowDiscriminator(rsd);
}
if (projectionContext.getOid() == null && rsd.getOrder() != 0) {
// Try to determine OID from lower-order contexts
for (LensProjectionContext aProjCtx: context.getProjectionContexts()) {
ResourceShadowDiscriminator aDiscr = aProjCtx.getResourceShadowDiscriminator();
if (rsd.equivalent(aDiscr) && aProjCtx.getOid() != null) {
projectionContext.setOid(aProjCtx.getOid());
break;
}
}
}
}
/**
* try to load focus context from the projections, e.g. by determining account owners
*/
public <F extends FocusType> void determineFocusContext(LensContext<F> context,
OperationResult result) throws ObjectNotFoundException, SchemaException {
if (context.getFocusContext() != null) {
// already done
return;
}
String focusOid = null;
PrismObject<F> focusObject = null;
LensProjectionContext projectionContextThatYeildedFocusOid = null;
for (LensProjectionContext projectionContext: context.getProjectionContexts()) {
String projectionOid = projectionContext.getOid();
if (projectionOid != null) {
PrismObject<F> shadowOwner = cacheRepositoryService.searchShadowOwner(projectionOid,
SelectorOptions.createCollection(GetOperationOptions.createAllowNotFound()),
result);
if (shadowOwner != null) {
if (focusOid == null || focusOid.equals(shadowOwner.getOid())) {
focusOid = shadowOwner.getOid();
focusObject = shadowOwner;
projectionContextThatYeildedFocusOid = projectionContext;
} else {
throw new IllegalArgumentException("The context does not have explicit focus. Attempt to determine focus failed because two " +
"projections points to different foci: "+projectionContextThatYeildedFocusOid+"->"+focusOid+"; "+
projectionContext+"->"+shadowOwner);
}
}
}
}
if (focusOid != null) {
LensFocusContext<F> focusContext = context.getOrCreateFocusContext(focusObject.getCompileTimeClass());
PrismObject<F> object = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusOid, null, result);
focusContext.setLoadedObject(object);
}
}
private <F extends ObjectType> void loadObjectCurrent(LensContext<F> context, OperationResult result) throws SchemaException, ObjectNotFoundException {
LensFocusContext<F> focusContext = context.getFocusContext();
if (focusContext == null) {
// Nothing to load
return;
}
// Make sure that we RELOAD the user object if the context is not fresh
// the user may have changed in the meantime
if (focusContext.getObjectCurrent() != null && focusContext.isFresh()) {
// already loaded
return;
}
ObjectDelta<F> objectDelta = focusContext.getDelta();
if (objectDelta != null && objectDelta.isAdd() && focusContext.getExecutedDeltas().isEmpty()) {
//we're adding the focal object. No need to load it, it is in the delta
focusContext.setFresh(true);
return;
}
if (focusContext.getObjectCurrent() != null && objectDelta != null && objectDelta.isDelete()) {
// do not reload if the delta is delete. the reload will most likely fail anyway
// but DO NOT set the fresh flag in this case, it may be misleading
return;
}
String userOid = focusContext.getOid();
if (StringUtils.isBlank(userOid)) {
throw new IllegalArgumentException("No OID in primary focus delta");
}
// Always load a complete object here, including the not-returned-by-default properties.
// This is temporary measure to make sure that the mappings will have all they need.
// See MID-2635
Collection<SelectorOptions<GetOperationOptions>> options =
SelectorOptions.createCollection(GetOperationOptions.createRetrieve(RetrieveOption.INCLUDE));
PrismObject<F> object = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), userOid, options, result);
focusContext.setLoadedObject(object);
focusContext.setFresh(true);
LOGGER.trace("Focal object loaded: {}", object);
}
private <O extends ObjectType> void setPrimaryDeltaOldValue(LensElementContext<O> ctx) throws SchemaException, ObjectNotFoundException {
if (ctx.getPrimaryDelta() != null && ctx.getObjectOld() != null && ctx.isModify()) {
PrismObject<O> objectOld = ctx.getObjectOld();
for (ItemDelta<?,?> itemDelta: ctx.getPrimaryDelta().getModifications()) {
LensUtil.setDeltaOldValue(ctx, itemDelta);
}
}
}
private <F extends ObjectType> void loadFromSystemConfig(LensContext<F> context, OperationResult result)
throws ObjectNotFoundException, SchemaException, ConfigurationException {
PrismObject<SystemConfigurationType> systemConfiguration = systemObjectCache.getSystemConfiguration(result);
if (systemConfiguration == null) {
// This happens in some tests. And also during first startup.
return;
}
context.setSystemConfiguration(systemConfiguration);
SystemConfigurationType systemConfigurationType = systemConfiguration.asObjectable();
if (context.getFocusContext() != null) {
PrismObject<F> object = context.getFocusContext().getObjectAny();
if (context.getFocusContext().getObjectPolicyConfigurationType() == null) {
List<String> subTypes = FocusTypeUtil.determineSubTypes(object);
ObjectPolicyConfigurationType policyConfigurationType =
ModelUtils.determineObjectPolicyConfiguration(context.getFocusContext().getObjectTypeClass(), subTypes,
systemConfigurationType);
LOGGER.trace("Selected policy configuration: {}", policyConfigurationType);
context.getFocusContext().setObjectPolicyConfigurationType(policyConfigurationType);
}
}
if (context.getFocusTemplate() == null) {
PrismObject<ObjectTemplateType> focusTemplate = determineFocusTemplate(context, result);
if (focusTemplate != null) {
context.setFocusTemplate(focusTemplate.asObjectable());
}
}
if (context.getAccountSynchronizationSettings() == null) {
ProjectionPolicyType globalAccountSynchronizationSettings = systemConfigurationType.getGlobalAccountSynchronizationSettings();
LOGGER.trace("Applying globalAccountSynchronizationSettings to context: {}", globalAccountSynchronizationSettings);
context.setAccountSynchronizationSettings(globalAccountSynchronizationSettings);
}
}
// expects that object policy configuration is already set in focusContext
private <F extends ObjectType> PrismObject<ObjectTemplateType> determineFocusTemplate(LensContext<F> context, OperationResult result) throws ObjectNotFoundException, SchemaException, ConfigurationException {
LensFocusContext<F> focusContext = context.getFocusContext();
if (focusContext == null) {
return null;
}
ObjectPolicyConfigurationType policyConfigurationType = focusContext.getObjectPolicyConfigurationType();
if (policyConfigurationType == null) {
LOGGER.trace("No default object template (no policy)");
return null;
}
ObjectReferenceType templateRef = policyConfigurationType.getObjectTemplateRef();
if (templateRef == null) {
LOGGER.trace("No default object template (no templateRef)");
return null;
}
PrismObject<ObjectTemplateType> template = cacheRepositoryService.getObject(ObjectTemplateType.class, templateRef.getOid(), null, result);
return template;
}
private <F extends FocusType> void loadLinkRefs(LensContext<F> context, Task task, OperationResult result) throws ObjectNotFoundException,
SchemaException, CommunicationException, ConfigurationException, SecurityViolationException, PolicyViolationException {
LensFocusContext<F> focusContext = context.getFocusContext();
if (focusContext == null) {
// Nothing to load
return;
}
LOGGER.trace("loadLinkRefs starting");
PrismObject<F> userCurrent = focusContext.getObjectCurrent();
if (userCurrent != null) {
loadLinkRefsFromFocus(context, userCurrent, task, result);
LOGGER.trace("loadLinkRefsFromFocus done");
}
if (consistencyChecks) context.checkConsistence();
loadLinkRefsFromDelta(context, userCurrent, focusContext.getPrimaryDelta(), task, result);
LOGGER.trace("loadLinkRefsFromDelta done");
if (consistencyChecks) context.checkConsistence();
loadProjectionContextsSync(context, task, result);
LOGGER.trace("loadProjectionContextsSync done");
if (consistencyChecks) context.checkConsistence();
}
/**
* Does not overwrite existing account contexts, just adds new ones.
*/
private <F extends FocusType> void loadLinkRefsFromFocus(LensContext<F> context, PrismObject<F> focus,
Task task, OperationResult result) throws ObjectNotFoundException,
CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException {
PrismReference linkRef = focus.findReference(FocusType.F_LINK_REF);
if (linkRef == null) {
return;
}
for (PrismReferenceValue linkRefVal : linkRef.getValues()) {
String oid = linkRefVal.getOid();
if (StringUtils.isBlank(oid)) {
LOGGER.trace("Null or empty OID in link reference {} in:\n{}", linkRef,
focus.debugDump(1));
throw new SchemaException("Null or empty OID in link reference in " + focus);
}
LensProjectionContext existingAccountContext = findAccountContext(oid, context);
if (!canBeLoaded(context, existingAccountContext)){
continue;
}
if (existingAccountContext != null) {
// TODO: do we need to reload the account inside here? yes we need
existingAccountContext.setFresh(true);
continue;
}
PrismObject<ShadowType> shadow = linkRefVal.getObject();
if (shadow == null) {
// Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here.
// We need to fetch from provisioning and not repository so the correct definition will be set.
GetOperationOptions rootOpts = GetOperationOptions.createNoFetch();
rootOpts.setPointInTimeType(PointInTimeType.FUTURE);
Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(rootOpts);
LOGGER.trace("Loading shadow {} from linkRef, options={}", oid, options);
try {
shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result);
} catch (ObjectNotFoundException e) {
// Broken accountRef. We need to mark it for deletion
LensProjectionContext accountContext = getOrCreateEmptyThombstoneProjectionContext(context, oid);
accountContext.setFresh(true);
accountContext.setExists(false);
OperationResult getObjectSubresult = result.getLastSubresult();
getObjectSubresult.setErrorsHandled();
continue;
}
} else {
// Make sure it has a proper definition. This may come from outside of the model.
provisioningService.applyDefinition(shadow, result);
}
LensProjectionContext accountContext = getOrCreateAccountContext(context, shadow, task, result);
accountContext.setFresh(true);
accountContext.setExists(shadow != null);
if (context.isDoReconciliationForAllProjections()) {
accountContext.setDoReconciliation(true);
}
if (accountContext.isDoReconciliation()) {
// Do not load old account now. It will get loaded later in the
// reconciliation step.
continue;
}
accountContext.setLoadedObject(shadow);
}
}
private <F extends FocusType> void loadLinkRefsFromDelta(LensContext<F> context, PrismObject<F> focus,
ObjectDelta<F> focusPrimaryDelta, Task task, OperationResult result) throws SchemaException,
ObjectNotFoundException, CommunicationException, ConfigurationException,
SecurityViolationException, PolicyViolationException {
if (focusPrimaryDelta == null) {
return;
}
ReferenceDelta linkRefDelta;
if (focusPrimaryDelta.getChangeType() == ChangeType.ADD) {
PrismReference linkRef = focusPrimaryDelta.getObjectToAdd().findReference(
FocusType.F_LINK_REF);
if (linkRef == null) {
// Adding new focus with no linkRef -> nothing to do
return;
}
linkRefDelta = linkRef.createDelta(new ItemPath(FocusType.F_LINK_REF));
linkRefDelta.addValuesToAdd(PrismValue.cloneValues(linkRef.getValues()));
} else if (focusPrimaryDelta.getChangeType() == ChangeType.MODIFY) {
linkRefDelta = focusPrimaryDelta.findReferenceModification(FocusType.F_LINK_REF);
if (linkRefDelta == null) {
return;
}
} else {
// delete, all existing account are already marked for delete
return;
}
if (linkRefDelta.isReplace()) {
// process "replace" by distributing values to delete and add
linkRefDelta = (ReferenceDelta) linkRefDelta.clone();
PrismReference linkRef = focus.findReference(FocusType.F_LINK_REF);
linkRefDelta.distributeReplace(linkRef == null ? null : linkRef.getValues());
}
if (linkRefDelta.getValuesToAdd() != null) {
for (PrismReferenceValue refVal : linkRefDelta.getValuesToAdd()) {
String oid = refVal.getOid();
LensProjectionContext accountContext = null;
PrismObject<ShadowType> shadow = null;
boolean isCombinedAdd = false;
if (oid == null) {
// Adding new account
shadow = refVal.getObject();
if (shadow == null) {
throw new SchemaException("Null or empty OID in account reference " + refVal + " in "
+ focus);
}
provisioningService.applyDefinition(shadow, result);
if (consistencyChecks) ShadowUtil.checkConsistence(shadow, "account from "+linkRefDelta);
// Check for conflicting change
accountContext = LensUtil.getProjectionContext(context, shadow, provisioningService, prismContext, task, result);
if (accountContext != null) {
// There is already existing context for the same discriminator. Tolerate this only if
// the deltas match. It is an error otherwise.
ObjectDelta<ShadowType> primaryDelta = accountContext.getPrimaryDelta();
if (primaryDelta == null) {
throw new SchemaException("Attempt to add "+shadow+" to a user that already contains "+
accountContext.getHumanReadableKind()+" of type '"+
accountContext.getResourceShadowDiscriminator().getIntent()+"' on "+accountContext.getResource());
}
if (!primaryDelta.isAdd()) {
throw new SchemaException("Conflicting changes in the context. " +
"Add of accountRef in the user delta with embedded object conflicts with explicit delta "+primaryDelta);
}
if (!shadow.equals(primaryDelta.getObjectToAdd())) {
throw new SchemaException("Conflicting changes in the context. " +
"Add of accountRef in the user delta with embedded object is not adding the same object as explicit delta "+primaryDelta);
}
} else {
// Create account context from embedded object
accountContext = createProjectionContext(context, shadow, task, result);
}
// This is a new account that is to be added. So it should
// go to account primary delta
ObjectDelta<ShadowType> accountPrimaryDelta = shadow.createAddDelta();
accountContext.setPrimaryDelta(accountPrimaryDelta);
accountContext.setFullShadow(true);
accountContext.setExists(false);
isCombinedAdd = true;
} else {
// We have OID. This is either linking of existing account or
// add of new account
// therefore check for account existence to decide
try {
// Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here.
// We need to fetch from provisioning and not repository so the correct definition will be set.
GetOperationOptions rootOpts = GetOperationOptions.createNoFetch();
rootOpts.setPointInTimeType(PointInTimeType.FUTURE);
Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(rootOpts);
shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result);
// Create account context from retrieved object
accountContext = getOrCreateAccountContext(context, shadow, task, result);
accountContext.setLoadedObject(shadow);
accountContext.setExists(true);
} catch (ObjectNotFoundException e) {
if (refVal.getObject() == null) {
// account does not exist, no composite account in
// ref -> this is really an error
throw e;
} else {
// New account (with OID)
result.muteLastSubresultError();
shadow = refVal.getObject();
if (!shadow.hasCompleteDefinition()) {
provisioningService.applyDefinition(shadow, result);
}
// Create account context from embedded object
accountContext = createProjectionContext(context, shadow, task, result);
ObjectDelta<ShadowType> accountPrimaryDelta = shadow.createAddDelta();
accountContext.setPrimaryDelta(accountPrimaryDelta);
accountContext.setFullShadow(true);
accountContext.setExists(false);
isCombinedAdd = true;
}
}
}
if (context.isDoReconciliationForAllProjections() && !isCombinedAdd) {
accountContext.setDoReconciliation(true);
}
accountContext.setFresh(true);
}
}
if (linkRefDelta.getValuesToDelete() != null) {
for (PrismReferenceValue refVal : linkRefDelta.getValuesToDelete()) {
String oid = refVal.getOid();
LensProjectionContext accountContext = null;
PrismObject<ShadowType> account = null;
if (oid == null) {
throw new SchemaException("Cannot delete account ref without an oid in " + focus);
} else {
try {
// Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here.
// We need to fetch from provisioning and not repository so the correct definition will be set.
Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(GetOperationOptions.createNoFetch());
account = provisioningService.getObject(ShadowType.class, oid, options, task, result);
// Create account context from retrieved object
accountContext = getOrCreateAccountContext(context, account, task, result);
accountContext.setLoadedObject(account);
accountContext.setExists(true);
} catch (ObjectNotFoundException e) {
try{
// Broken accountRef. We need to try again with raw options, because the error should be thrown because of non-existent resource
Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(GetOperationOptions.createRaw());
account = provisioningService.getObject(ShadowType.class, oid, options, task, result);
accountContext = getOrCreateEmptyThombstoneProjectionContext(context, oid);
accountContext.setFresh(true);
accountContext.setExists(false);
OperationResult getObjectSubresult = result.getLastSubresult();
getObjectSubresult.setErrorsHandled();
} catch (ObjectNotFoundException ex){
// This is still OK. It means deleting an accountRef
// that points to non-existing object
// just log a warning
LOGGER.warn("Deleting accountRef of " + focus + " that points to non-existing OID "
+ oid);
}
}
}
if (accountContext != null) {
if (refVal.getObject() == null) {
accountContext.setSynchronizationIntent(SynchronizationIntent.UNLINK);
} else {
accountContext.setSynchronizationIntent(SynchronizationIntent.DELETE);
ObjectDelta<ShadowType> accountPrimaryDelta = account.createDeleteDelta();
accountContext.setPrimaryDelta(accountPrimaryDelta);
}
accountContext.setFresh(true);
}
}
}
// remove the accountRefs without oid. These will get into the way now.
// The accounts
// are in the context now and will be linked at the end of the process
// (it they survive the policy)
// We need to make sure this happens on the real primary user delta
if (focusPrimaryDelta.getChangeType() == ChangeType.ADD) {
focusPrimaryDelta.getObjectToAdd().removeReference(FocusType.F_LINK_REF);
} else if (focusPrimaryDelta.getChangeType() == ChangeType.MODIFY) {
focusPrimaryDelta.removeReferenceModification(FocusType.F_LINK_REF);
}
}
private <F extends ObjectType> void loadProjectionContextsSync(LensContext<F> context, Task task, OperationResult result) throws SchemaException,
ObjectNotFoundException, CommunicationException, ConfigurationException,
SecurityViolationException {
for (LensProjectionContext projCtx : context.getProjectionContexts()) {
if (projCtx.isFresh() && projCtx.getObjectCurrent() != null) {
// already loaded
continue;
}
ObjectDelta<ShadowType> syncDelta = projCtx.getSyncDelta();
if (syncDelta != null) {
if (projCtx.isDoReconciliation()) {
// Do not load old account now. It will get loaded later in the
// reconciliation step. Just mark it as fresh.
projCtx.setFresh(true);
continue;
}
String oid = syncDelta.getOid();
PrismObject<ShadowType> shadow = null;
if (syncDelta.getChangeType() == ChangeType.ADD) {
shadow = syncDelta.getObjectToAdd().clone();
projCtx.setLoadedObject(shadow);
projCtx.setExists(true);
} else {
if (oid == null) {
throw new IllegalArgumentException("No OID in sync delta in " + projCtx);
}
// Using NO_FETCH so we avoid reading in a full account. This is more efficient as we don't need full account here.
// We need to fetch from provisioning and not repository so the correct definition will be set.
GetOperationOptions option = GetOperationOptions.createNoFetch();
option.setDoNotDiscovery(true);
option.setPointInTimeType(PointInTimeType.FUTURE);
Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(option);
try {
shadow = provisioningService.getObject(ShadowType.class, oid, options, task, result);
} catch (ObjectNotFoundException e) {
LOGGER.trace("Loading shadow {} from sync delta failed: not found", oid);
projCtx.setExists(false);
projCtx.setObjectCurrent(null);
}
// We will not set old account if the delta is delete. The
// account does not really exists now.
// (but the OID and resource will be set from the repo
// shadow)
if (syncDelta.getChangeType() == ChangeType.DELETE) {
projCtx.setExists(false);
projCtx.getResourceShadowDiscriminator().setThombstone(true);
} else if (shadow != null) {
syncDelta.applyTo(shadow);
projCtx.setLoadedObject(shadow);
projCtx.setExists(true);
}
}
// Make sure OID is set correctly
projCtx.setOid(oid);
// Make sure that resource is also resolved
if (projCtx.getResource() == null && shadow != null) {
String resourceOid = ShadowUtil.getResourceOid(shadow.asObjectable());
if (resourceOid == null) {
throw new IllegalArgumentException("No resource OID in " + shadow);
}
ResourceType resourceType = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result);
projCtx.setResource(resourceType);
}
projCtx.setFresh(true);
}
}
}
private <F extends ObjectType> boolean canBeLoaded(LensContext<F> context, LensProjectionContext projCtx){
if (QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_DISCOVERY).equals(context.getChannel()) && projCtx == null && ModelExecuteOptions.isLimitPropagation(context.getOptions())) {
// avoid to create projection context if the channel which
// triggered this operation is discovery..we need only
// projection context of discovered shadow
return false;
}
return true;
}
private <F extends FocusType> LensProjectionContext getOrCreateAccountContext(LensContext<F> context,
PrismObject<ShadowType> projection, Task task, OperationResult result) throws ObjectNotFoundException,
CommunicationException, SchemaException, ConfigurationException, SecurityViolationException, PolicyViolationException {
ShadowType accountType = projection.asObjectable();
String resourceOid = ShadowUtil.getResourceOid(accountType);
if (resourceOid == null) {
throw new SchemaException("The " + projection + " has null resource reference OID");
}
LensProjectionContext projectionContext = context.findProjectionContextByOid(accountType.getOid());
if (projectionContext == null) {
String intent = ShadowUtil.getIntent(accountType);
ShadowKindType kind = ShadowUtil.getKind(accountType);
ResourceType resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result);
intent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext);
boolean thombstone = false;
if (ShadowUtil.isDead(accountType)) {
thombstone = true;
}
ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, intent, thombstone);
projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd);
if (projectionContext.getOid() == null) {
projectionContext.setOid(projection.getOid());
} else if (projection.getOid() != null && !projectionContext.getOid().equals(projection.getOid())) {
// Conflict. We have existing projection and another project that is added (with the same discriminator).
// Chances are that the old object is already deleted (e.g. during rename). So let's be
// slightly inefficient here and check for existing shadow existence
try {
GetOperationOptions rootOpt = GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE);
rootOpt.setDoNotDiscovery(true);
Collection<SelectorOptions<GetOperationOptions>> opts = SelectorOptions.createCollection(rootOpt);
LOGGER.trace("Projection conflict detected, exsting: {}, new {}", projectionContext.getOid(), projection.getOid());
PrismObject<ShadowType> existingShadow = provisioningService.getObject(ShadowType.class, projectionContext.getOid(), opts, task, result);
// Maybe it is the other way around
try {
PrismObject<ShadowType> newShadow = provisioningService.getObject(ShadowType.class, projection.getOid(), opts, task, result);
// Obviously, two projections with the same discriminator exists
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Projection {} already exists in context\nExisting:\n{}\nNew:\n{}", new Object[]{rsd,
existingShadow.debugDump(1), newShadow.debugDump(1)});
}
throw new PolicyViolationException("Projection "+rsd+" already exists in context (existing "+existingShadow+", new "+projection);
} catch (ObjectNotFoundException e) {
// This is somehow expected, fix it and we can go on
result.muteLastSubresultError();
// We have to create new context in this case, but it has to have thumbstone set
rsd.setThombstone(true);
projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd);
// We have to mark it as dead right now, otherwise the uniqueness check may fail
markShadowDead(projection.getOid(), result);
}
} catch (ObjectNotFoundException e) {
// This is somehow expected, fix it and we can go on
result.muteLastSubresultError();
String shadowOid = projectionContext.getOid();
projectionContext.getResourceShadowDiscriminator().setThombstone(true);
projectionContext = LensUtil.getOrCreateProjectionContext(context, rsd);
// We have to mark it as dead right now, otherwise the uniqueness check may fail
markShadowDead(shadowOid, result);
}
}
}
return projectionContext;
}
private void markShadowDead(String oid, OperationResult result) {
if (oid == null) {
// nothing to mark
return;
}
Collection<? extends ItemDelta<?, ?>> modifications = MiscSchemaUtil.createCollection(PropertyDelta.createReplaceDelta(prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(ShadowType.class),
ShadowType.F_DEAD, true));
try {
cacheRepositoryService.modifyObject(ShadowType.class, oid, modifications, result);
// TODO report to task?
} catch (ObjectNotFoundException e) {
// Done already
result.muteLastSubresultError();
} catch (ObjectAlreadyExistsException | SchemaException e) {
// Should not happen
throw new SystemException(e.getMessage(), e);
}
}
private <F extends FocusType> LensProjectionContext createProjectionContext(LensContext<F> context,
PrismObject<ShadowType> account, Task task, OperationResult result) throws ObjectNotFoundException,
CommunicationException, SchemaException, ConfigurationException, SecurityViolationException {
ShadowType shadowType = account.asObjectable();
String resourceOid = ShadowUtil.getResourceOid(shadowType);
if (resourceOid == null) {
throw new SchemaException("The " + account + " has null resource reference OID");
}
String intent = ShadowUtil.getIntent(shadowType);
ShadowKindType kind = ShadowUtil.getKind(shadowType);
ResourceType resource = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result);
String accountIntent = LensUtil.refineProjectionIntent(kind, intent, resource, prismContext);
ResourceShadowDiscriminator rsd = new ResourceShadowDiscriminator(resourceOid, kind, accountIntent);
LensProjectionContext accountSyncContext = context.findProjectionContext(rsd);
if (accountSyncContext != null) {
throw new SchemaException("Attempt to add "+account+" to a user that already contains account of type '"+accountIntent+"' on "+resource);
}
accountSyncContext = context.createProjectionContext(rsd);
accountSyncContext.setResource(resource);
accountSyncContext.setOid(account.getOid());
return accountSyncContext;
}
private <F extends ObjectType> LensProjectionContext findAccountContext(String accountOid, LensContext<F> context) {
for (LensProjectionContext accContext : context.getProjectionContexts()) {
if (accountOid.equals(accContext.getOid())) {
return accContext;
}
}
return null;
}
private <F extends ObjectType> LensProjectionContext getOrCreateEmptyThombstoneProjectionContext(LensContext<F> context,
String missingShadowOid) {
LensProjectionContext projContext = context.findProjectionContextByOid(missingShadowOid);
if (projContext == null) {
projContext = context.createProjectionContext(null);
projContext.setOid(missingShadowOid);
}
if (projContext.getResourceShadowDiscriminator() == null) {
projContext.setResourceShadowDiscriminator(new ResourceShadowDiscriminator(null, null, null, true));
} else {
projContext.getResourceShadowDiscriminator().setThombstone(true);
}
projContext.setFullShadow(false);
projContext.setObjectCurrent(null);
return projContext;
}
/**
* Check reconcile flag in account sync context and set accountOld
* variable if it's not set (from provisioning), load resource (if not set already), etc.
*/
private <F extends ObjectType> void finishLoadOfProjectionContext(LensContext<F> context,
LensProjectionContext projContext, Task task, OperationResult result)
throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException,
SecurityViolationException {
String projectionHumanReadableName = projContext.getHumanReadableName();
if (projContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) {
return;
}
// MID-2436 (volatile objects) - as a quick but effective hack, we set reconciliation:=TRUE for volatile accounts
ResourceObjectTypeDefinitionType objectDefinition = projContext.getResourceObjectTypeDefinitionType();
if (objectDefinition != null && objectDefinition.getVolatility() == ResourceObjectVolatilityType.UNPREDICTABLE && !projContext.isDoReconciliation()) {
LOGGER.trace("Resource object volatility is UNPREDICTABLE => setting doReconciliation to TRUE for {}", projContext.getResourceShadowDiscriminator());
projContext.setDoReconciliation(true);
}
// Remember OID before the object could be wiped
String projectionObjectOid = projContext.getOid();
if (projContext.isDoReconciliation() && !projContext.isFullShadow()) {
// The current object is useless here. So lets just wipe it so it will get loaded
projContext.setObjectCurrent(null);
}
// Load current object
boolean thombstone = false;
PrismObject<ShadowType> projectionObject = projContext.getObjectCurrent();
if (projContext.getObjectCurrent() == null || needToReload(context, projContext)) {
if (projContext.isAdd()) {
// No need to load old object, there is none
projContext.setExists(false);
projContext.recompute();
projectionObject = projContext.getObjectNew();
} else {
if (projectionObjectOid == null) {
projContext.setExists(false);
if (projContext.getResourceShadowDiscriminator() == null || projContext.getResourceShadowDiscriminator().getResourceOid() == null) {
throw new SystemException(
"Projection "+projectionHumanReadableName+" with null OID, no representation and no resource OID in account sync context "+projContext);
}
} else {
projContext.setExists(true);
GetOperationOptions rootOptions = GetOperationOptions.createPointInTimeType(PointInTimeType.FUTURE);
if (projContext.isDoReconciliation()) {
if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) {
// Avoid discovery loops
rootOptions.setDoNotDiscovery(true);
}
} else {
rootOptions.setNoFetch(true);
}
rootOptions.setAllowNotFound(true);
Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(rootOptions);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Loading shadow {} for projection {}, options={}", projectionObjectOid, projectionHumanReadableName, options);
}
try {
PrismObject<ShadowType> objectOld = provisioningService.getObject(
projContext.getObjectTypeClass(), projectionObjectOid, options, task, result);
if (LOGGER.isTraceEnabled()) {
if (!GetOperationOptions.isNoFetch(rootOptions) && !GetOperationOptions.isRaw(rootOptions)) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Full shadow loaded for {}:\n{}", projectionHumanReadableName, objectOld.debugDump(1));
}
}
}
Validate.notNull(objectOld.getOid());
if (InternalsConfig.consistencyChecks) {
String resourceOid = projContext.getResourceOid();
if (resourceOid != null && !resourceOid.equals(objectOld.asObjectable().getResourceRef().getOid())) {
throw new IllegalStateException("Loaded shadow with wrong resourceRef. Loading shadow "+projectionObjectOid+", got "+
objectOld.getOid()+", expected resourceRef "+resourceOid+", but was "+objectOld.asObjectable().getResourceRef().getOid()+
" for context "+projectionHumanReadableName);
}
}
projContext.setLoadedObject(objectOld);
ShadowType oldShadow = objectOld.asObjectable();
if (projContext.isDoReconciliation()) {
projContext.determineFullShadowFlag(oldShadow.getFetchResult());
} else {
projContext.setFullShadow(false);
}
projectionObject = objectOld;
} catch (ObjectNotFoundException ex) {
// This does not mean BROKEN. The projection was there, but it gone now. What we really want here
// is a thombstone projection.
thombstone = true;
projContext.setFullShadow(false);
LOGGER.warn("Could not find object with oid {}. The projection context {} is marked as thombstone.", projectionObjectOid, projectionHumanReadableName);
} catch (CommunicationException | SchemaException | ConfigurationException | SecurityViolationException
| RuntimeException | Error e) {
LOGGER.warn("Problem while getting object with oid {}. Projection context {} is marked as broken: {}: {}",
projectionObjectOid, projectionHumanReadableName, e.getClass().getSimpleName(), e.getMessage());
projContext.setSynchronizationPolicyDecision(SynchronizationPolicyDecision.BROKEN);
ResourceType resourceType = projContext.getResource();
if (resourceType == null) {
throw e;
} else {
ErrorSelectorType errorSelector = null;
if (resourceType.getConsistency() != null) {
errorSelector = resourceType.getConsistency().getConnectorErrorCriticality();
}
if (errorSelector == null) {
if (e instanceof SchemaException) {
// Just continue evaluation. The error is recorded in the result.
// The consistency mechanism has (most likely) already done the best.
// We cannot do any better.
return;
} else {
throw e;
}
} else {
if (ExceptionUtil.isSelected(errorSelector, e)) {
throw e;
} else {
return;
}
}
}
}
}
projContext.setFresh(true);
}
} else {
projectionObject = projContext.getObjectCurrent();
if (projectionObjectOid != null) {
projContext.setExists(true);
}
}
// Determine Resource
ResourceType resourceType = projContext.getResource();
String resourceOid = null;
if (resourceType == null) {
if (projectionObject != null) {
ShadowType shadowType = projectionObject.asObjectable();
resourceOid = ShadowUtil.getResourceOid(shadowType);
} else if (projContext.getResourceShadowDiscriminator() != null) {
resourceOid = projContext.getResourceShadowDiscriminator().getResourceOid();
} else if (!thombstone) {
throw new IllegalStateException("No shadow, no discriminator and not thombstone? That won't do. Projection "+projectionHumanReadableName);
}
} else {
resourceOid = resourceType.getOid();
}
// Determine discriminator
ResourceShadowDiscriminator discr = projContext.getResourceShadowDiscriminator();
if (discr == null) {
if (projectionObject != null) {
ShadowType accountShadowType = projectionObject.asObjectable();
String intent = ShadowUtil.getIntent(accountShadowType);
ShadowKindType kind = ShadowUtil.getKind(accountShadowType);
discr = new ResourceShadowDiscriminator(resourceOid, kind, intent, thombstone);
} else {
discr = new ResourceShadowDiscriminator(null, null, null, thombstone);
}
projContext.setResourceShadowDiscriminator(discr);
} else {
if (thombstone) {
// We do not want to reset thombstone flag if it was set before
discr.setThombstone(thombstone);
}
}
// Load resource
if (resourceType == null && resourceOid != null) {
resourceType = LensUtil.getResourceReadOnly(context, resourceOid, provisioningService, task, result);
projContext.setResource(resourceType);
}
//Determine refined schema and password policies for account type
RefinedObjectClassDefinition structuralObjectClassDef = projContext.getStructuralObjectClassDefinition();
if (structuralObjectClassDef != null) {
ObjectReferenceType passwordPolicyRef = structuralObjectClassDef.getPasswordPolicy();
if (passwordPolicyRef != null && passwordPolicyRef.getOid() != null) {
PrismObject<ValuePolicyType> passwordPolicy = cacheRepositoryService.getObject(
ValuePolicyType.class, passwordPolicyRef.getOid(), null, result);
if (passwordPolicy != null) {
projContext.setAccountPasswordPolicy(passwordPolicy.asObjectable());
}
}
}
//set limitation, e.g. if this projection context should be recomputed and processed by projector
if (ModelExecuteOptions.isLimitPropagation(context.getOptions())){
if (context.getTriggeredResourceOid() != null){
if (!context.getTriggeredResourceOid().equals(resourceOid)){
projContext.setCanProject(false);
}
}
}
setPrimaryDeltaOldValue(projContext);
}
private <F extends ObjectType> boolean needToReload(LensContext<F> context,
LensProjectionContext projContext) {
ResourceShadowDiscriminator discr = projContext.getResourceShadowDiscriminator();
if (discr == null) {
return false;
}
// This is kind of brutal. But effective. We are reloading all higher-order dependencies
// before they are processed. This makes sure we have fresh state when they are re-computed.
// Because higher-order dependencies may have more than one projection context and the
// changes applied to one of them are not automatically reflected on on other. therefore we need to reload.
if (discr.getOrder() == 0) {
return false;
}
int executionWave = context.getExecutionWave();
int projCtxWave = projContext.getWave();
if (executionWave == projCtxWave - 1) {
// Reload right before its execution wave
return true;
}
return false;
}
private <F extends ObjectType> void fullCheckConsistence(LensContext<F> context) {
context.checkConsistence();
for (LensProjectionContext projectionContext: context.getProjectionContexts()) {
if (projectionContext.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) {
continue;
}
if (projectionContext.getResourceShadowDiscriminator() == null) {
throw new IllegalStateException("No discriminator in "+projectionContext);
}
}
}
public <F extends ObjectType> void loadFullShadow(LensContext<F> context, LensProjectionContext projCtx, Task task, OperationResult result)
throws ObjectNotFoundException, CommunicationException, SchemaException, ConfigurationException, SecurityViolationException {
if (projCtx.isFullShadow()) {
// already loaded
return;
}
if (projCtx.isAdd() && projCtx.getOid() == null) {
// nothing to load yet
return;
}
if (projCtx.isThombstone()) {
// loading is futile
return;
}
ResourceShadowDiscriminator discr = projCtx.getResourceShadowDiscriminator();
if (discr != null && discr.getOrder() > 0) {
// It may be just too early to load the projection
if (LensUtil.hasLowerOrderContext(context, projCtx) && (context.getExecutionWave() < projCtx.getWave())) {
// We cannot reliably load the context now
return;
}
}
GetOperationOptions getOptions = GetOperationOptions.createAllowNotFound();
getOptions.setPointInTimeType(PointInTimeType.FUTURE);
if (SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI.equals(context.getChannel())) {
LOGGER.trace("Loading full resource object {} from provisioning - with doNotDiscover to avoid loops", projCtx);
// Avoid discovery loops
getOptions.setDoNotDiscovery(true);
} else {
LOGGER.trace("Loading full resource object {} from provisioning (discovery enabled), channel: {}", projCtx, context.getChannel());
}
try {
Collection<SelectorOptions<GetOperationOptions>> options = SelectorOptions.createCollection(getOptions);
applyAttributesToGet(projCtx, options);
PrismObject<ShadowType> objectCurrent = provisioningService.getObject(ShadowType.class,
projCtx.getOid(), options, task, result);
Validate.notNull(objectCurrent.getOid());
// TODO: use setLoadedObject() instead?
projCtx.setObjectCurrent(objectCurrent);
ShadowType oldShadow = objectCurrent.asObjectable();
projCtx.determineFullShadowFlag(oldShadow.getFetchResult());
// The getObject may return different OID than we have requested in case that compensation happened
// TODO: this probably need to be fixed in the consistency mechanism
// TODO: the following line is a temporary fix
projCtx.setOid(objectCurrent.getOid());
} catch (ObjectNotFoundException ex) {
LOGGER.trace("Load of full resource object {} ended with ObjectNotFoundException (options={})", projCtx, getOptions);
if (projCtx.isDelete()){
//this is OK, shadow was deleted, but we will continue in processing with old shadow..and set it as full so prevent from other full loading
projCtx.setFullShadow(true);
} else {
boolean compensated = false;
if (!GetOperationOptions.isDoNotDiscovery(getOptions)) {
// The account might have been re-created by the discovery.
// Reload focus, try to find out if there is a new matching link (and the old is gone)
LensFocusContext<F> focusContext = context.getFocusContext();
if (focusContext != null) {
Class<F> focusClass = focusContext.getObjectTypeClass();
if (FocusType.class.isAssignableFrom(focusClass)) {
LOGGER.trace("Reloading focus to check for new links");
PrismObject<F> focusCurrent = cacheRepositoryService.getObject(focusContext.getObjectTypeClass(), focusContext.getOid(), null, result);
FocusType focusType = (FocusType) focusCurrent.asObjectable();
for (ObjectReferenceType linkRef: focusType.getLinkRef()) {
if (linkRef.getOid().equals(projCtx.getOid())) {
// The deleted shadow is still in the linkRef. This should not happen, but it obviously happens sometimes.
// Maybe some strange race condition? Anyway, we want a robust behavior and this linkeRef should NOT be there.
// So simple remove it.
LOGGER.warn("The OID "+projCtx.getOid()+" of deleted shadow still exists in the linkRef after discovery ("+focusCurrent+"), removing it");
ReferenceDelta unlinkDelta = ReferenceDelta.createModificationDelete(
FocusType.F_LINK_REF, focusContext.getObjectDefinition(), linkRef.asReferenceValue().clone());
focusContext.swallowToSecondaryDelta(unlinkDelta);
continue;
}
boolean found = false;
for (LensProjectionContext pCtx: context.getProjectionContexts()) {
if (linkRef.getOid().equals(pCtx.getOid())) {
found = true;
break;
}
}
if (!found) {
// This link is new, it is not in the existing lens context
PrismObject<ShadowType> newLinkRepoShadow = cacheRepositoryService.getObject(ShadowType.class, linkRef.getOid(), null, result);
if (ShadowUtil.matches(newLinkRepoShadow, projCtx.getResourceShadowDiscriminator())) {
LOGGER.trace("Found new matching link: {}, updating projection context", newLinkRepoShadow);
LOGGER.trace("Applying definition from provisioning first."); // MID-3317
provisioningService.applyDefinition(newLinkRepoShadow, result);
projCtx.setObjectCurrent(newLinkRepoShadow);
projCtx.setOid(newLinkRepoShadow.getOid());
projCtx.recompute();
compensated = true;
break;
} else {
LOGGER.trace("Found new link: {}, but skipping it because it does not match the projection context", newLinkRepoShadow);
}
}
}
}
}
}
if (!compensated) {
LOGGER.trace("ObjectNotFound error is not compensated, setting context to thombstone");
projCtx.getResourceShadowDiscriminator().setThombstone(true);
projCtx.setExists(false);
projCtx.setFullShadow(false);
}
}
}
projCtx.recompute();
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Loaded full resource object:\n{}", projCtx.debugDump(1));
}
}
private void applyAttributesToGet(LensProjectionContext projCtx, Collection<SelectorOptions<GetOperationOptions>> options) throws SchemaException {
if ( !LensUtil.isPasswordReturnedByDefault(projCtx)
&& LensUtil.needsFullShadowForCredentialProcessing(projCtx)) {
options.add(SelectorOptions.create(SchemaConstants.PATH_PASSWORD_VALUE, GetOperationOptions.createRetrieve()));
}
}
}