/*
* 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;
import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.FOCUS_OPERATION;
import static com.evolveum.midpoint.model.api.ProgressInformation.ActivityType.RESOURCE_OBJECT_OPERATION;
import static com.evolveum.midpoint.model.api.ProgressInformation.StateType.ENTERING;
import static com.evolveum.midpoint.schema.internals.InternalsConfig.consistencyChecks;
import com.evolveum.midpoint.common.Clock;
import com.evolveum.midpoint.common.SynchronizationUtils;
import com.evolveum.midpoint.model.api.ModelAuthorizationAction;
import com.evolveum.midpoint.model.api.ModelExecuteOptions;
import com.evolveum.midpoint.model.api.ProgressInformation;
import com.evolveum.midpoint.model.api.context.SynchronizationPolicyDecision;
import com.evolveum.midpoint.model.common.expression.Expression;
import com.evolveum.midpoint.model.common.expression.ExpressionEvaluationContext;
import com.evolveum.midpoint.model.common.expression.ExpressionFactory;
import com.evolveum.midpoint.model.common.expression.ExpressionUtil;
import com.evolveum.midpoint.model.common.expression.ExpressionVariables;
import com.evolveum.midpoint.model.impl.ModelObjectResolver;
import com.evolveum.midpoint.model.impl.expr.ModelExpressionThreadLocalHolder;
import com.evolveum.midpoint.model.impl.lens.projector.FocusConstraintsChecker;
import com.evolveum.midpoint.model.impl.lens.projector.PolicyRuleProcessor;
import com.evolveum.midpoint.model.impl.lens.projector.credentials.CredentialsProcessor;
import com.evolveum.midpoint.model.impl.util.Utils;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.crypto.EncryptionException;
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.PrismValueDeltaSetTriple;
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.prism.xnode.PrimitiveXNode;
import com.evolveum.midpoint.provisioning.api.ProvisioningOperationOptions;
import com.evolveum.midpoint.provisioning.api.ProvisioningService;
import com.evolveum.midpoint.repo.api.RepoAddOptions;
import com.evolveum.midpoint.repo.api.RepositoryService;
import com.evolveum.midpoint.schema.GetOperationOptions;
import com.evolveum.midpoint.schema.ObjectDeltaOperation;
import com.evolveum.midpoint.schema.PointInTimeType;
import com.evolveum.midpoint.schema.ResourceShadowDiscriminator;
import com.evolveum.midpoint.schema.SelectorOptions;
import com.evolveum.midpoint.schema.constants.ObjectTypes;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.internals.InternalsConfig;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.result.OperationResultStatus;
import com.evolveum.midpoint.schema.util.ShadowUtil;
import com.evolveum.midpoint.security.api.OwnerResolver;
import com.evolveum.midpoint.security.api.SecurityEnforcer;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.*;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.prism.xml.ns._public.types_3.RawType;
import org.apache.commons.lang.BooleanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
/**
* @author semancik
*/
@Component
public class ChangeExecutor {
private static final Trace LOGGER = TraceManager.getTrace(ChangeExecutor.class);
private static final String OPERATION_EXECUTE_DELTA = ChangeExecutor.class.getName() + ".executeDelta";
private static final String OPERATION_EXECUTE = ChangeExecutor.class.getName() + ".execute";
private static final String OPERATION_EXECUTE_FOCUS = OPERATION_EXECUTE + ".focus";
private static final String OPERATION_EXECUTE_PROJECTION = OPERATION_EXECUTE + ".projection";
private static final String OPERATION_LINK_ACCOUNT = ChangeExecutor.class.getName() + ".linkShadow";
private static final String OPERATION_UNLINK_ACCOUNT = ChangeExecutor.class.getName() + ".unlinkShadow";
private static final String OPERATION_UPDATE_SITUATION_ACCOUNT = ChangeExecutor.class.getName()
+ ".updateSituationInShadow";
@Autowired
private transient TaskManager taskManager;
@Autowired
@Qualifier("cacheRepositoryService")
private transient RepositoryService cacheRepositoryService;
@Autowired
private ProvisioningService provisioning;
@Autowired
private PrismContext prismContext;
@Autowired
private ExpressionFactory expressionFactory;
@Autowired
private SecurityEnforcer securityEnforcer;
@Autowired
private Clock clock;
@Autowired
private ModelObjectResolver objectResolver;
@Autowired
private OperationalDataManager metadataManager;
@Autowired
private PolicyRuleProcessor policyRuleProcessor;
@Autowired
private CredentialsProcessor credentialsProcessor;
private PrismObjectDefinition<UserType> userDefinition = null;
private PrismObjectDefinition<ShadowType> shadowDefinition = null;
@PostConstruct
private void locateDefinitions() {
userDefinition = prismContext.getSchemaRegistry()
.findObjectDefinitionByCompileTimeClass(UserType.class);
shadowDefinition = prismContext.getSchemaRegistry()
.findObjectDefinitionByCompileTimeClass(ShadowType.class);
}
// returns true if current operation has to be restarted, see
// ObjectAlreadyExistsException handling (TODO specify more exactly)
public <O extends ObjectType> boolean executeChanges(LensContext<O> context, Task task,
OperationResult parentResult) throws ObjectAlreadyExistsException, ObjectNotFoundException,
SchemaException, CommunicationException, ConfigurationException,
SecurityViolationException, ExpressionEvaluationException {
OperationResult result = parentResult.createSubresult(OPERATION_EXECUTE);
// FOCUS
context.checkAbortRequested();
LensFocusContext<O> focusContext = context.getFocusContext();
if (focusContext != null) {
ObjectDelta<O> focusDelta = focusContext.getWaveExecutableDelta(context.getExecutionWave());
focusDelta = policyRuleProcessor.applyAssignmentSituation(context, focusDelta);
if (focusDelta != null) {
ObjectPolicyConfigurationType objectPolicyConfigurationType = focusContext
.getObjectPolicyConfigurationType();
applyObjectPolicy(focusContext, focusDelta, objectPolicyConfigurationType);
OperationResult subResult = result.createSubresult(
OPERATION_EXECUTE_FOCUS + "." + focusContext.getObjectTypeClass().getSimpleName());
try {
// Will remove credential deltas or hash them
focusDelta = credentialsProcessor.transformFocusExectionDelta(context, focusDelta);
} catch (EncryptionException e) {
recordFatalError(subResult, result, null, e);
result.computeStatus();
throw new SystemException(e.getMessage(), e);
}
try {
context.reportProgress(new ProgressInformation(FOCUS_OPERATION, ENTERING));
executeDelta(focusDelta, focusContext, context, null, null, task, subResult);
subResult.computeStatus();
} catch (SchemaException | ObjectNotFoundException | CommunicationException | ConfigurationException | SecurityViolationException | ExpressionEvaluationException | RuntimeException e) {
recordFatalError(subResult, result, null, e);
throw e;
} catch (ObjectAlreadyExistsException e) {
subResult.computeStatus();
if (!subResult.isSuccess() && !subResult.isHandledError()) {
subResult.recordFatalError(e);
}
result.computeStatusComposite();
throw e;
} finally {
context.reportProgress(new ProgressInformation(FOCUS_OPERATION, subResult));
}
} else {
LOGGER.trace("Skipping focus change execute, because user delta is null");
}
}
// PROJECTIONS
context.checkAbortRequested();
boolean restartRequested = false;
for (LensProjectionContext projCtx : context.getProjectionContexts()) {
if (projCtx.getWave() != context.getExecutionWave()) {
continue;
}
if (!projCtx.isCanProject()) {
continue;
}
// we should not get here, but just to be sure
if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.IGNORE) {
LOGGER.trace("Skipping ignored projection context {}", projCtx.toHumanReadableString());
continue;
}
OperationResult subResult = result.createSubresult(
OPERATION_EXECUTE_PROJECTION + "." + projCtx.getObjectTypeClass().getSimpleName());
subResult.addContext("discriminator", projCtx.getResourceShadowDiscriminator());
if (projCtx.getResource() != null) {
subResult.addParam("resource", projCtx.getResource().getName());
}
try {
context.checkAbortRequested();
context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION,
projCtx.getResourceShadowDiscriminator(), ENTERING));
executeReconciliationScript(projCtx, context, BeforeAfterType.BEFORE, task, subResult);
ObjectDelta<ShadowType> projDelta = projCtx.getExecutableDelta();
if (shouldBeDeleted(projDelta, projCtx)) {
projDelta = ObjectDelta.createDeleteDelta(projCtx.getObjectTypeClass(), projCtx.getOid(),
prismContext);
}
if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) {
if (context.getFocusContext() != null
&& context.getFocusContext().getDelta() != null
&& context.getFocusContext().getDelta().isDelete()
&& context.getOptions() != null
&& ModelExecuteOptions.isForce(context.getOptions())) {
if (projDelta == null) {
projDelta = ObjectDelta.createDeleteDelta(projCtx.getObjectTypeClass(),
projCtx.getOid(), prismContext);
}
}
if (projDelta != null && projDelta.isDelete()) {
executeDelta(projDelta, projCtx, context, null, projCtx.getResource(), task,
subResult);
}
} else {
if (projDelta == null || projDelta.isEmpty()) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("No change for " + projCtx.getResourceShadowDiscriminator());
}
if (focusContext != null) {
updateLinks(focusContext, projCtx, task, subResult);
}
// Make sure post-reconcile delta is always executed,
// even if there is no change
executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task,
subResult);
subResult.computeStatus();
subResult.recordNotApplicableIfUnknown();
continue;
} else if (projDelta.isDelete() && projCtx.getResourceShadowDiscriminator() != null
&& projCtx.getResourceShadowDiscriminator().getOrder() > 0) {
// HACK ... for higher-order context check if this was
// already deleted
LensProjectionContext lowerOrderContext = LensUtil.findLowerOrderContext(context,
projCtx);
if (lowerOrderContext != null && lowerOrderContext.isDelete()) {
// We assume that this was already executed
subResult.setStatus(OperationResultStatus.NOT_APPLICABLE);
continue;
}
}
executeDelta(projDelta, projCtx, context, null, projCtx.getResource(), task, subResult);
}
subResult.computeStatus();
if (focusContext != null) {
updateLinks(focusContext, projCtx, task, subResult);
}
executeReconciliationScript(projCtx, context, BeforeAfterType.AFTER, task, subResult);
subResult.computeStatus();
subResult.recordNotApplicableIfUnknown();
} catch (SchemaException e) {
recordProjectionExecutionException(e, projCtx, subResult,
SynchronizationPolicyDecision.BROKEN);
continue;
} catch (ObjectNotFoundException e) {
recordProjectionExecutionException(e, projCtx, subResult,
SynchronizationPolicyDecision.BROKEN);
continue;
} catch (ObjectAlreadyExistsException e) {
// check if this is a repeated attempt - OAEE was not handled
// correctly, e.g. if creating "Users" user in AD, whereas
// "Users" is SAM Account Name which is used by a built-in group
// - in such case, mark the context as broken
if (isRepeatedAlreadyExistsException(projCtx)) {
recordProjectionExecutionException(e, projCtx, subResult,
SynchronizationPolicyDecision.BROKEN);
continue;
}
// in his case we do not need to set account context as
// broken, instead we need to restart projector for this
// context to recompute new account or find out if the
// account was already linked..
// and also do not set fatal error to the operation result, this
// is a special case
// if it is fatal, it will be set later
// but we need to set some result
subResult.recordSuccess();
subResult.muteLastSubresultError();
restartRequested = true;
break; // we will process remaining projections when retrying
// the wave
} catch (CommunicationException e) {
recordProjectionExecutionException(e, projCtx, subResult,
SynchronizationPolicyDecision.BROKEN);
continue;
} catch (ConfigurationException e) {
recordProjectionExecutionException(e, projCtx, subResult,
SynchronizationPolicyDecision.BROKEN);
continue;
} catch (SecurityViolationException e) {
recordProjectionExecutionException(e, projCtx, subResult,
SynchronizationPolicyDecision.BROKEN);
continue;
} catch (ExpressionEvaluationException e) {
recordProjectionExecutionException(e, projCtx, subResult,
SynchronizationPolicyDecision.BROKEN);
continue;
} catch (RuntimeException e) {
recordProjectionExecutionException(e, projCtx, subResult,
SynchronizationPolicyDecision.BROKEN);
continue;
} finally {
context.reportProgress(new ProgressInformation(RESOURCE_OBJECT_OPERATION,
projCtx.getResourceShadowDiscriminator(), subResult));
}
}
// Result computation here needs to be slightly different
result.computeStatusComposite();
return restartRequested;
}
private boolean shouldBeDeleted(ObjectDelta<ShadowType> accDelta, LensProjectionContext accCtx) {
return (accDelta == null || accDelta.isEmpty())
&& (accCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE
|| accCtx.getSynchronizationIntent() == SynchronizationIntent.DELETE);
}
private <O extends ObjectType> boolean isRepeatedAlreadyExistsException(
LensProjectionContext projContext) {
int deltas = projContext.getExecutedDeltas().size();
LOGGER.trace("isRepeatedAlreadyExistsException starting; number of executed deltas = {}", deltas);
if (deltas < 2) {
return false;
}
LensObjectDeltaOperation<ShadowType> lastDeltaOp = projContext.getExecutedDeltas().get(deltas - 1);
LensObjectDeltaOperation<ShadowType> previousDeltaOp = projContext.getExecutedDeltas()
.get(deltas - 2);
// TODO check also previous execution result to see if it's
// AlreadyExistException?
ObjectDelta<ShadowType> lastDelta = lastDeltaOp.getObjectDelta();
ObjectDelta<ShadowType> previousDelta = previousDeltaOp.getObjectDelta();
boolean rv;
if (lastDelta.isAdd() && previousDelta.isAdd()) {
rv = isEquivalentAddDelta(lastDelta.getObjectToAdd(), previousDelta.getObjectToAdd());
} else if (lastDelta.isModify() && previousDelta.isModify()) {
rv = isEquivalentModifyDelta(lastDelta.getModifications(), previousDelta.getModifications());
} else {
rv = false;
}
LOGGER.trace(
"isRepeatedAlreadyExistsException returning {}; based of comparison of previousDelta:\n{}\nwith lastDelta:\n{}",
rv, previousDelta, lastDelta);
return rv;
}
private boolean isEquivalentModifyDelta(Collection<? extends ItemDelta<?, ?>> modifications1,
Collection<? extends ItemDelta<?, ?>> modifications2) {
Collection<? extends ItemDelta<?, ?>> attrDeltas1 = ItemDelta.findItemDeltasSubPath(modifications1,
new ItemPath(ShadowType.F_ATTRIBUTES));
Collection<? extends ItemDelta<?, ?>> attrDeltas2 = ItemDelta.findItemDeltasSubPath(modifications2,
new ItemPath(ShadowType.F_ATTRIBUTES));
return MiscUtil.unorderedCollectionEquals(attrDeltas1, attrDeltas2);
}
private boolean isEquivalentAddDelta(PrismObject<ShadowType> object1, PrismObject<ShadowType> object2) {
PrismContainer attributes1 = object1.findContainer(ShadowType.F_ATTRIBUTES);
PrismContainer attributes2 = object2.findContainer(ShadowType.F_ATTRIBUTES);
if (attributes1 == null || attributes2 == null || attributes1.size() != 1
|| attributes2.size() != 1) { // suspicious cases
return false;
}
return attributes1.getValue().equivalent(attributes2.getValue());
}
private <O extends ObjectType> void applyObjectPolicy(LensFocusContext<O> focusContext,
ObjectDelta<O> focusDelta, ObjectPolicyConfigurationType objectPolicyConfigurationType) {
if (objectPolicyConfigurationType == null) {
return;
}
PrismObject<O> objectNew = focusContext.getObjectNew();
if (focusDelta.isAdd() && objectNew.getOid() == null) {
for (PropertyConstraintType propertyConstraintType : objectPolicyConfigurationType
.getPropertyConstraint()) {
if (BooleanUtils.isTrue(propertyConstraintType.isOidBound())) {
ItemPath itemPath = propertyConstraintType.getPath().getItemPath();
PrismProperty<Object> prop = objectNew.findProperty(itemPath);
String stringValue = prop.getRealValue().toString();
focusContext.setOid(stringValue);
}
}
// deprecated
if (BooleanUtils.isTrue(objectPolicyConfigurationType.isOidNameBoundMode())) {
String name = objectNew.asObjectable().getName().getOrig();
focusContext.setOid(name);
}
}
}
private <P extends ObjectType> void recordProjectionExecutionException(Exception e,
LensProjectionContext accCtx, OperationResult subResult, SynchronizationPolicyDecision decision) {
subResult.recordFatalError(e);
LOGGER.error("Error executing changes for {}: {}",
new Object[] { accCtx.toHumanReadableString(), e.getMessage(), e });
if (decision != null) {
accCtx.setSynchronizationPolicyDecision(decision);
}
}
private void recordFatalError(OperationResult subResult, OperationResult result, String message,
Throwable e) {
if (message == null) {
message = e.getMessage();
}
subResult.recordFatalError(e);
if (result != null) {
result.computeStatusComposite();
}
}
/**
* Make sure that the account is linked (or unlinked) as needed.
*/
private <O extends ObjectType, F extends FocusType> void updateLinks(
LensFocusContext<O> focusObjectContext, LensProjectionContext projCtx, Task task,
OperationResult result) throws ObjectNotFoundException, SchemaException {
if (focusObjectContext == null) {
return;
}
Class<O> objectTypeClass = focusObjectContext.getObjectTypeClass();
if (!FocusType.class.isAssignableFrom(objectTypeClass)) {
return;
}
LensFocusContext<F> focusContext = (LensFocusContext<F>) focusObjectContext;
if (projCtx.getResourceShadowDiscriminator() != null
&& projCtx.getResourceShadowDiscriminator().getOrder() > 0) {
// Don't mess with links for higher-order contexts. The link should
// be dealt with
// during processing of zero-order context.
return;
}
String projOid = projCtx.getOid();
if (projOid == null) {
if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.BROKEN) {
// This seems to be OK. In quite a strange way, but still OK.
return;
}
LOGGER.trace("Shadow has null OID, this should not happen, context:\n{}", projCtx.debugDump());
throw new IllegalStateException("Shadow has null OID, this should not happen");
}
if (linkShouldExist(projCtx, result)) {
// Link should exist
PrismObject<F> objectCurrent = focusContext.getObjectCurrent();
if (objectCurrent != null) {
for (ObjectReferenceType linkRef : objectCurrent.asObjectable().getLinkRef()) {
if (projOid.equals(linkRef.getOid())) {
// Already linked, nothing to do, only be sure, the
// situation is set with the good value
LOGGER.trace("Updating situation in already linked shadow.");
updateSituationInShadow(task, SynchronizationSituationType.LINKED, focusObjectContext,
projCtx, result);
return;
}
}
}
// Not linked, need to link
linkShadow(focusContext.getOid(), projOid, focusObjectContext, projCtx, task, result);
// be sure, that the situation is set correctly
LOGGER.trace("Updating situation after shadow was linked.");
updateSituationInShadow(task, SynchronizationSituationType.LINKED, focusObjectContext, projCtx,
result);
} else {
// Link should NOT exist
if (!focusContext.isDelete()) {
PrismObject<F> objectCurrent = focusContext.getObjectCurrent();
// it is possible that objectCurrent is null (and objectNew is
// non-null), in case of User ADD operation (MID-2176)
if (objectCurrent != null) {
PrismReference linkRef = objectCurrent.findReference(FocusType.F_LINK_REF);
if (linkRef != null) {
for (PrismReferenceValue linkRefVal : linkRef.getValues()) {
if (linkRefVal.getOid().equals(projOid)) {
// Linked, need to unlink
unlinkShadow(focusContext.getOid(), linkRefVal, focusObjectContext, projCtx,
task, result);
}
}
}
}
}
if (projCtx.isDelete() || projCtx.isThombstone()) {
LOGGER.trace("Resource object {} deleted, updating also situation in shadow.", projOid);
// HACK HACK?
try {
updateSituationInShadow(task, SynchronizationSituationType.DELETED, focusObjectContext,
projCtx, result);
} catch (ObjectNotFoundException e) {
// HACK HACK?
LOGGER.trace(
"Resource object {} is gone, cannot update situation in shadow (this is probably harmless).",
projOid);
result.getLastSubresult().setStatus(OperationResultStatus.HANDLED_ERROR);
}
} else {
// This should NOT be UNLINKED. We just do not know the
// situation here. Reflect that in the shadow.
LOGGER.trace("Resource object {} unlinked from the user, updating also situation in shadow.",
projOid);
updateSituationInShadow(task, null, focusObjectContext, projCtx, result);
}
// Not linked, that's OK
}
}
private boolean linkShouldExist(LensProjectionContext projCtx, OperationResult result) {
if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.UNLINK) {
return false;
}
if (isEmptyThombstone(projCtx)) {
return false;
}
if (projCtx.hasPendingOperations()) {
return true;
}
if (projCtx.getSynchronizationPolicyDecision() == SynchronizationPolicyDecision.DELETE
|| projCtx.isDelete()) {
if (result.isInProgress()) {
// Keep the link until operation is finished
return true;
} else {
return false;
}
}
return true;
}
/**
* Return true if this projection is just a linkRef that points to no
* shadow.
*/
private boolean isEmptyThombstone(LensProjectionContext projCtx) {
return projCtx.getResourceShadowDiscriminator() != null
&& projCtx.getResourceShadowDiscriminator().isThombstone()
&& projCtx.getObjectCurrent() == null;
}
private <F extends ObjectType> void linkShadow(String userOid, String shadowOid,
LensElementContext<F> focusContext, LensProjectionContext projCtx, Task task,
OperationResult parentResult) throws ObjectNotFoundException, SchemaException {
Class<F> typeClass = focusContext.getObjectTypeClass();
if (!FocusType.class.isAssignableFrom(typeClass)) {
return;
}
String channel = focusContext.getLensContext().getChannel();
LOGGER.debug("Linking shadow " + shadowOid + " to focus " + userOid);
OperationResult result = parentResult.createSubresult(OPERATION_LINK_ACCOUNT);
PrismReferenceValue linkRef = new PrismReferenceValue();
linkRef.setOid(shadowOid);
linkRef.setTargetType(ShadowType.COMPLEX_TYPE);
Collection<? extends ItemDelta> linkRefDeltas = ReferenceDelta
.createModificationAddCollection(FocusType.F_LINK_REF, getUserDefinition(), linkRef);
try {
cacheRepositoryService.modifyObject(typeClass, userOid, linkRefDeltas, result);
task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid,
ChangeType.MODIFY, channel, null);
} catch (ObjectAlreadyExistsException ex) {
task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid,
ChangeType.MODIFY, channel, ex);
throw new SystemException(ex);
} catch (Throwable t) {
task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, userOid,
ChangeType.MODIFY, channel, t);
throw t;
} finally {
result.computeStatus();
ObjectDelta<F> userDelta = ObjectDelta.createModifyDelta(userOid, linkRefDeltas, typeClass,
prismContext);
LensObjectDeltaOperation<F> userDeltaOp = LensUtil.createObjectDeltaOperation(userDelta, result,
focusContext, projCtx);
focusContext.addToExecutedDeltas(userDeltaOp);
}
}
private PrismObjectDefinition<UserType> getUserDefinition() {
return userDefinition;
}
private <F extends ObjectType> void unlinkShadow(String focusOid, PrismReferenceValue accountRef,
LensElementContext<F> focusContext, LensProjectionContext projCtx, Task task,
OperationResult parentResult) throws ObjectNotFoundException, SchemaException {
Class<F> typeClass = focusContext.getObjectTypeClass();
if (!FocusType.class.isAssignableFrom(typeClass)) {
return;
}
String channel = focusContext.getLensContext().getChannel();
LOGGER.debug("Unlinking shadow " + accountRef.getOid() + " from focus " + focusOid);
OperationResult result = parentResult.createSubresult(OPERATION_UNLINK_ACCOUNT);
Collection<? extends ItemDelta> accountRefDeltas = ReferenceDelta.createModificationDeleteCollection(
FocusType.F_LINK_REF, getUserDefinition(), accountRef.clone());
try {
cacheRepositoryService.modifyObject(typeClass, focusOid, accountRefDeltas, result);
task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid,
ChangeType.MODIFY, channel, null);
} catch (ObjectAlreadyExistsException ex) {
task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid,
ChangeType.MODIFY, channel, ex);
result.recordFatalError(ex);
throw new SystemException(ex);
} catch (Throwable t) {
task.recordObjectActionExecuted(focusContext.getObjectAny(), typeClass, focusOid,
ChangeType.MODIFY, channel, t);
throw t;
} finally {
result.computeStatus();
ObjectDelta<F> userDelta = ObjectDelta.createModifyDelta(focusOid, accountRefDeltas, typeClass,
prismContext);
LensObjectDeltaOperation<F> userDeltaOp = LensUtil.createObjectDeltaOperation(userDelta, result,
focusContext, projCtx);
focusContext.addToExecutedDeltas(userDeltaOp);
}
}
private <F extends ObjectType> void updateSituationInShadow(Task task,
SynchronizationSituationType situation, LensFocusContext<F> focusContext,
LensProjectionContext projectionCtx, OperationResult parentResult)
throws ObjectNotFoundException, SchemaException {
String projectionOid = projectionCtx.getOid();
OperationResult result = new OperationResult(OPERATION_UPDATE_SITUATION_ACCOUNT);
result.addParam("situation", situation);
result.addParam("accountRef", projectionOid);
PrismObject<ShadowType> account = null;
GetOperationOptions getOptions = GetOperationOptions.createNoFetch();
getOptions.setAllowNotFound(true);
try {
account = provisioning.getObject(ShadowType.class, projectionOid,
SelectorOptions.createCollection(getOptions), task, result);
} catch (Exception ex) {
LOGGER.trace("Problem with getting account, skipping modifying situation in account.");
return;
}
List<PropertyDelta<?>> syncSituationDeltas = SynchronizationUtils
.createSynchronizationSituationAndDescriptionDelta(account, situation, task.getChannel(),
projectionCtx.hasFullShadow());
try {
Utils.setRequestee(task, focusContext);
ProvisioningOperationOptions options = ProvisioningOperationOptions
.createCompletePostponed(false);
options.setDoNotDiscovery(true);
String changedOid = provisioning.modifyObject(ShadowType.class, projectionOid,
syncSituationDeltas, null, options, task, result);
// modifyProvisioningObject(AccountShadowType.class, accountRef,
// syncSituationDeltas,
// ProvisioningOperationOptions.createCompletePostponed(false),
// task, result);
projectionCtx.setSynchronizationSituationResolved(situation);
LOGGER.trace("Situation in projection {} was updated to {}.", projectionCtx, situation);
} catch (ObjectNotFoundException ex) {
// if the object not found exception is thrown, it's ok..probably
// the account was deleted by previous execution of changes..just
// log in the trace the message for the user..
LOGGER.trace(
"Situation in account could not be updated. Account not found on the resource. Skipping modifying situation in account");
return;
} catch (Exception ex) {
throw new SystemException(ex.getMessage(), ex);
} finally {
Utils.clearRequestee(task);
}
// if everything is OK, add result of the situation modification to the
// parent result
result.recordSuccess();
parentResult.addSubresult(result);
}
private <T extends ObjectType, F extends ObjectType> void executeDelta(ObjectDelta<T> objectDelta,
LensElementContext<T> objectContext, LensContext<F> context, ModelExecuteOptions options,
ResourceType resource, Task task, OperationResult parentResult)
throws ObjectAlreadyExistsException, ObjectNotFoundException, SchemaException,
CommunicationException, ConfigurationException, SecurityViolationException,
ExpressionEvaluationException {
if (objectDelta == null) {
throw new IllegalArgumentException("Null change");
}
if (objectDelta.getOid() == null) {
objectDelta.setOid(objectContext.getOid());
}
objectDelta = computeDeltaToExecute(objectDelta, objectContext);
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("computeDeltaToExecute returned:\n{}",
objectDelta != null ? objectDelta.debugDump() : "(null)");
}
if (objectDelta == null || objectDelta.isEmpty()) {
LOGGER.debug("Skipping execution of delta because it was already executed: {}", objectContext);
return;
}
if (InternalsConfig.consistencyChecks) {
objectDelta.checkConsistence(ConsistencyCheckScope.fromBoolean(consistencyChecks));
}
// Other types than focus types may not be definition-complete (e.g.
// accounts and resources are completed in provisioning)
if (FocusType.class.isAssignableFrom(objectDelta.getObjectTypeClass())) {
objectDelta.assertDefinitions();
}
LensUtil.setDeltaOldValue(objectContext, objectDelta);
if (LOGGER.isTraceEnabled()) {
logDeltaExecution(objectDelta, context, resource, null, task);
}
OperationResult result = parentResult.createSubresult(OPERATION_EXECUTE_DELTA);
try {
if (objectDelta.getChangeType() == ChangeType.ADD) {
executeAddition(objectDelta, context, objectContext, options, resource, task, result);
} else if (objectDelta.getChangeType() == ChangeType.MODIFY) {
executeModification(objectDelta, context, objectContext, options, resource, task, result);
} else if (objectDelta.getChangeType() == ChangeType.DELETE) {
executeDeletion(objectDelta, context, objectContext, options, resource, task, result);
}
// To make sure that the OID is set (e.g. after ADD operation)
LensUtil.setContextOid(context, objectContext, objectDelta.getOid());
} finally {
result.computeStatus();
if (objectContext != null) {
if (!objectDelta.hasCompleteDefinition()) {
throw new SchemaException("object delta does not have complete definition");
}
LensObjectDeltaOperation<T> objectDeltaOp = LensUtil.createObjectDeltaOperation(
objectDelta.clone(), result, objectContext, null, resource);
objectContext.addToExecutedDeltas(objectDeltaOp);
}
if (LOGGER.isDebugEnabled()) {
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("EXECUTION result {}", result.getLastSubresult());
} else {
// Execution of deltas was not logged yet
logDeltaExecution(objectDelta, context, resource, result.getLastSubresult(), task);
}
}
}
}
private <T extends ObjectType, F extends FocusType> void removeExecutedItemDeltas(
ObjectDelta<T> objectDelta, LensElementContext<T> objectContext) {
if (objectContext == null) {
return;
}
if (objectDelta == null || objectDelta.isEmpty()) {
return;
}
if (objectDelta.getModifications() == null || objectDelta.getModifications().isEmpty()) {
return;
}
List<LensObjectDeltaOperation<T>> executedDeltas = objectContext.getExecutedDeltas();
for (LensObjectDeltaOperation<T> executedDelta : executedDeltas) {
ObjectDelta<T> executed = executedDelta.getObjectDelta();
Iterator<? extends ItemDelta> objectDeltaIterator = objectDelta.getModifications().iterator();
while (objectDeltaIterator.hasNext()) {
ItemDelta d = objectDeltaIterator.next();
if (executed.containsModification(d, true, true) || d.isEmpty()) {
objectDeltaIterator.remove();
}
}
}
}
// TODO beware - what if the delta was executed but not successfully?
private <T extends ObjectType, F extends FocusType> boolean alreadyExecuted(ObjectDelta<T> objectDelta,
LensElementContext<T> objectContext) {
if (objectContext == null) {
return false;
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Checking for already executed delta:\n{}\nIn deltas:\n{}", objectDelta.debugDump(),
DebugUtil.debugDump(objectContext.getExecutedDeltas()));
}
return ObjectDeltaOperation.containsDelta(objectContext.getExecutedDeltas(), objectDelta);
}
/**
* Was this object already added? (temporary method, should be removed soon)
*/
private <T extends ObjectType> boolean wasAdded(List<LensObjectDeltaOperation<T>> executedOperations,
String oid) {
for (LensObjectDeltaOperation operation : executedOperations) {
if (operation.getObjectDelta().isAdd() && oid.equals(operation.getObjectDelta().getOid())
&& !operation.getExecutionResult().isFatalError()) {
return true;
}
}
return false;
}
/**
* Computes delta to execute, given a list of already executes deltas. See
* below.
*/
private <T extends ObjectType> ObjectDelta<T> computeDeltaToExecute(ObjectDelta<T> objectDelta,
LensElementContext<T> objectContext) {
if (objectContext == null) {
return objectDelta;
}
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("Computing delta to execute from delta:\n{}\nGiven these executed deltas:\n{}",
objectDelta.debugDump(), DebugUtil.debugDump(objectContext.getExecutedDeltas()));
}
List<LensObjectDeltaOperation<T>> executedDeltas = objectContext.getExecutedDeltas();
return computeDiffDelta(executedDeltas, objectDelta);
}
/**
* Compute a "difference delta" - given that executedDeltas were executed,
* and objectDelta is about to be executed; eliminates parts that have
* already been done. It is meant as a kind of optimization (for MODIFY
* deltas) and error avoidance (for ADD deltas).
*
* Explanation for ADD deltas: there are situations where an execution wave
* is restarted - when unexpected AlreadyExistsException is reported from
* provisioning. However, in such cases, duplicate ADD Focus deltas were
* generated. So we (TEMPORARILY!) decided to filter them out here.
*
* Unfortunately, this mechanism is not well-defined, and seems to work more
* "by accident" than "by design". It should be replaced with something more
* serious. Perhaps by re-reading current focus state when repeating a wave?
* Actually, it is a supplement for rewriting ADD->MODIFY deltas in
* LensElementContext.getFixedPrimaryDelta. That method converts primary
* deltas (and as far as I know, that is the only place where this problem
* should occur). Nevertheless, for historical and safety reasons I keep
* also the processing in this method.
*
* Anyway, currently it treats only two cases: 1) if the objectDelta is
* present in the list of executed deltas 2) if the objectDelta is ADD, and
* another ADD delta is there (then the difference is computed)
*
*/
private <T extends ObjectType> ObjectDelta<T> computeDiffDelta(
List<? extends ObjectDeltaOperation<T>> executedDeltas, ObjectDelta<T> objectDelta) {
if (executedDeltas == null || executedDeltas.isEmpty()) {
return objectDelta;
}
ObjectDeltaOperation<T> lastRelated = findLastRelatedDelta(executedDeltas, objectDelta); // any
// delta
// related
// to
// our
// OID,
// not
// ending
// with
// fatal
// error
if (LOGGER.isTraceEnabled()) {
LOGGER.trace("findLastRelatedDelta returned:\n{}",
lastRelated != null ? lastRelated.debugDump() : "(null)");
}
if (lastRelated == null) {
return objectDelta; // nothing found, let us apply our delta
}
if (lastRelated.getExecutionResult().isSuccess() && lastRelated.containsDelta(objectDelta)) {
return null; // case 1 - exact match found with SUCCESS result,
// let's skip the processing of our delta
}
if (!objectDelta.isAdd()) {
return objectDelta; // MODIFY or DELETE delta - we may safely apply
// it (not 100% sure about DELETE case)
}
// determine if we got case 2
if (lastRelated.getObjectDelta().isDelete()) {
return objectDelta; // we can (and should) apply the ADD delta as a
// whole, because the object was deleted
}
// let us treat the most simple case here - meaning we have existing ADD
// delta and nothing more
// TODO add more sophistication if needed
if (!lastRelated.getObjectDelta().isAdd()) {
return objectDelta; // this will probably fail, but ...
}
// at this point we know that ADD was more-or-less successfully
// executed, so let's compute the difference, creating a MODIFY delta
PrismObject<T> alreadyAdded = lastRelated.getObjectDelta().getObjectToAdd();
PrismObject<T> toBeAddedNow = objectDelta.getObjectToAdd();
return alreadyAdded.diff(toBeAddedNow);
}
private <T extends ObjectType> ObjectDeltaOperation<T> findLastRelatedDelta(
List<? extends ObjectDeltaOperation<T>> executedDeltas, ObjectDelta<T> objectDelta) {
for (int i = executedDeltas.size() - 1; i >= 0; i--) {
ObjectDeltaOperation<T> currentOdo = executedDeltas.get(i);
if (!currentOdo.getExecutionResult().isFatalError()) {
ObjectDelta<T> current = currentOdo.getObjectDelta();
if (current.equals(objectDelta)) {
return currentOdo;
}
String oid1 = current.isAdd() ? current.getObjectToAdd().getOid() : current.getOid();
String oid2 = objectDelta.isAdd() ? objectDelta.getObjectToAdd().getOid()
: objectDelta.getOid();
if (oid1 != null && oid2 != null) {
if (oid1.equals(oid2)) {
return currentOdo;
}
continue;
}
// ADD-MODIFY and ADD-DELETE combinations lead to applying whole
// delta (as a result of computeDiffDelta)
// so we can be lazy and check only ADD-ADD combinations here...
if (!current.isAdd() || !objectDelta.isAdd()) {
continue;
}
// we simply check the type (for focus objects) and
// resource+kind+intent (for shadows)
PrismObject<T> currentObject = current.getObjectToAdd();
PrismObject<T> objectTypeToAdd = objectDelta.getObjectToAdd();
Class currentObjectClass = currentObject.getCompileTimeClass();
Class objectTypeToAddClass = objectTypeToAdd.getCompileTimeClass();
if (currentObjectClass == null || !currentObjectClass.equals(objectTypeToAddClass)) {
continue;
}
if (FocusType.class.isAssignableFrom(currentObjectClass)) {
return currentOdo; // we suppose there is only one delta of
// Focus class
}
// Shadow deltas have to be matched exactly... because "ADD
// largo" and "ADD largo1" are two different deltas
// And, this is not a big problem, because ADD conflicts are
// treated by provisioning
// Again, all this stuff is highly temporary and has to be
// thrown away as soon as possible!!!
continue;
// if (ShadowType.class.equals(currentObjectClass)) {
// ShadowType currentShadow = (ShadowType)
// currentObject.asObjectable();
// ShadowType shadowToAdd = (ShadowType)
// objectTypeToAdd.asObjectable();
// if (currentShadow.getResourceRef() != null &&
// shadowToAdd.getResourceRef() != null &&
// currentShadow.getResourceRef().getOid().equals(shadowToAdd.getResourceRef().getOid())
// &&
// currentShadow.getKind() == shadowToAdd.getKind() && // TODO
// default kind handling
// currentShadow.getIntent() != null &&
// currentShadow.getIntent().equals(shadowToAdd.getIntent())) {
// // TODO default intent handling
// return currentOdo;
// }
// }
}
}
return null;
}
private ProvisioningOperationOptions copyFromModelOptions(ModelExecuteOptions options) {
ProvisioningOperationOptions provisioningOptions = new ProvisioningOperationOptions();
if (options == null) {
return provisioningOptions;
}
provisioningOptions.setForce(options.getForce());
provisioningOptions.setOverwrite(options.getOverwrite());
return provisioningOptions;
}
private <F extends ObjectType> ProvisioningOperationOptions getProvisioningOptions(LensContext<F> context,
ModelExecuteOptions modelOptions) {
if (modelOptions == null && context != null) {
modelOptions = context.getOptions();
}
ProvisioningOperationOptions provisioningOptions = copyFromModelOptions(modelOptions);
if (context != null && context.getChannel() != null) {
if (context.getChannel().equals(QNameUtil.qNameToUri(SchemaConstants.CHANGE_CHANNEL_RECON))) {
// TODO: this is probably wrong. We should not have special case
// for recon channel! This should be handled by the provisioning task
// setting the right options there.
provisioningOptions.setCompletePostponed(false);
}
if (context.getChannel().equals(SchemaConstants.CHANGE_CHANNEL_DISCOVERY_URI)) {
// We want to avoid endless loops in error handling.
provisioningOptions.setDoNotDiscovery(true);
}
}
return provisioningOptions;
}
private <T extends ObjectType, F extends ObjectType> void logDeltaExecution(ObjectDelta<T> objectDelta,
LensContext<F> context, ResourceType resource, OperationResult result, Task task) {
StringBuilder sb = new StringBuilder();
sb.append("---[ ");
if (result == null) {
sb.append("Going to EXECUTE");
} else {
sb.append("EXECUTED");
}
sb.append(" delta of ").append(objectDelta.getObjectTypeClass().getSimpleName());
sb.append(" ]---------------------\n");
DebugUtil.debugDumpLabel(sb, "Channel", 0);
sb.append(" ").append(LensUtil.getChannel(context, task)).append("\n");
if (context != null) {
DebugUtil.debugDumpLabel(sb, "Wave", 0);
sb.append(" ").append(context.getExecutionWave()).append("\n");
}
if (resource != null) {
sb.append("Resource: ").append(resource.toString()).append("\n");
}
sb.append(objectDelta.debugDump());
sb.append("\n");
if (result != null) {
DebugUtil.debugDumpLabel(sb, "Result", 0);
sb.append(" ").append(result.getStatus()).append(": ").append(result.getMessage());
}
sb.append("\n--------------------------------------------------");
LOGGER.debug("\n{}", sb);
}
private <F extends ObjectType> OwnerResolver createOwnerResolver(final LensContext<F> context, Task task,
OperationResult result) {
return new LensOwnerResolver<>(context, objectResolver, task, result);
}
private <T extends ObjectType, F extends ObjectType> void executeAddition(ObjectDelta<T> change,
final LensContext<F> context, LensElementContext<T> objectContext, ModelExecuteOptions options,
ResourceType resource, Task task, OperationResult result) throws ObjectAlreadyExistsException,
ObjectNotFoundException, SchemaException, CommunicationException, ConfigurationException,
SecurityViolationException, ExpressionEvaluationException {
PrismObject<T> objectToAdd = change.getObjectToAdd();
if (change.getModifications() != null) {
for (ItemDelta delta : change.getModifications()) {
delta.applyTo(objectToAdd);
}
change.getModifications().clear();
}
OwnerResolver ownerResolver = createOwnerResolver(context, task, result);
try {
securityEnforcer.authorize(ModelAuthorizationAction.ADD.getUrl(),
AuthorizationPhaseType.EXECUTION, objectToAdd, null, null, ownerResolver, result);
T objectTypeToAdd = objectToAdd.asObjectable();
metadataManager.applyMetadataAdd(context, objectToAdd, clock.currentTimeXMLGregorianCalendar(), task, result);
if (options == null && context != null) {
options = context.getOptions();
}
String oid;
if (objectTypeToAdd instanceof TaskType) {
oid = addTask((TaskType) objectTypeToAdd, result);
} else if (objectTypeToAdd instanceof NodeType) {
throw new UnsupportedOperationException("NodeType cannot be added using model interface");
} else if (ObjectTypes.isManagedByProvisioning(objectTypeToAdd)) {
ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options);
oid = addProvisioningObject(objectToAdd, context, objectContext, provisioningOptions,
resource, task, result);
if (oid == null) {
throw new SystemException(
"Provisioning addObject returned null OID while adding " + objectToAdd);
}
result.addReturn("createdAccountOid", oid);
} else {
FocusConstraintsChecker.clearCacheFor(objectToAdd.asObjectable().getName());
RepoAddOptions addOpt = new RepoAddOptions();
if (ModelExecuteOptions.isOverwrite(options)) {
addOpt.setOverwrite(true);
}
if (ModelExecuteOptions.isNoCrypt(options)) {
addOpt.setAllowUnencryptedValues(true);
}
oid = cacheRepositoryService.addObject(objectToAdd, addOpt, result);
if (oid == null) {
throw new SystemException(
"Repository addObject returned null OID while adding " + objectToAdd);
}
}
change.setOid(oid);
task.recordObjectActionExecuted(objectToAdd, objectToAdd.getCompileTimeClass(), oid,
ChangeType.ADD, context.getChannel(), null);
} catch (Throwable t) {
task.recordObjectActionExecuted(objectToAdd, objectToAdd.getCompileTimeClass(), null,
ChangeType.ADD, context.getChannel(), t);
throw t;
}
}
private <T extends ObjectType, F extends ObjectType> void executeDeletion(ObjectDelta<T> change,
LensContext<F> context, LensElementContext<T> objectContext, ModelExecuteOptions options,
ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException,
ObjectAlreadyExistsException, SchemaException, CommunicationException,
ConfigurationException, SecurityViolationException, ExpressionEvaluationException {
String oid = change.getOid();
Class<T> objectTypeClass = change.getObjectTypeClass();
PrismObject<T> objectOld = objectContext.getObjectOld();
OwnerResolver ownerResolver = createOwnerResolver(context, task, result);
try {
securityEnforcer.authorize(ModelAuthorizationAction.DELETE.getUrl(),
AuthorizationPhaseType.EXECUTION, objectOld, null, null, ownerResolver, result);
if (TaskType.class.isAssignableFrom(objectTypeClass)) {
taskManager.deleteTask(oid, result);
} else if (NodeType.class.isAssignableFrom(objectTypeClass)) {
taskManager.deleteNode(oid, result);
} else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) {
ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options);
try {
deleteProvisioningObject(objectTypeClass, oid, context, objectContext,
provisioningOptions, resource, task, result);
} catch (ObjectNotFoundException e) {
// Object that we wanted to delete is already gone. This can
// happen in some race conditions.
// As the resulting state is the same as we wanted it to be
// we will not complain and we will go on.
LOGGER.trace("Attempt to delete object {} ({}) that is already gone", oid,
objectTypeClass);
result.muteLastSubresultError();
}
} else {
try {
cacheRepositoryService.deleteObject(objectTypeClass, oid, result);
} catch (ObjectNotFoundException e) {
// Object that we wanted to delete is already gone. This can
// happen in some race conditions.
// As the resulting state is the same as we wanted it to be
// we will not complain and we will go on.
LOGGER.trace("Attempt to delete object {} ({}) that is already gone", oid,
objectTypeClass);
result.muteLastSubresultError();
}
}
task.recordObjectActionExecuted(objectOld, objectTypeClass, oid, ChangeType.DELETE,
context.getChannel(), null);
} catch (Throwable t) {
task.recordObjectActionExecuted(objectOld, objectTypeClass, oid, ChangeType.DELETE,
context.getChannel(), t);
throw t;
}
}
private <T extends ObjectType, F extends ObjectType> void executeModification(ObjectDelta<T> change,
LensContext<F> context, LensElementContext<T> objectContext, ModelExecuteOptions options,
ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException,
SchemaException, ObjectAlreadyExistsException, CommunicationException,
ConfigurationException, SecurityViolationException, ExpressionEvaluationException {
Class<T> objectTypeClass = change.getObjectTypeClass();
PrismObject<T> objectNew = objectContext.getObjectNew();
OwnerResolver ownerResolver = createOwnerResolver(context, task, result);
try {
securityEnforcer.authorize(ModelAuthorizationAction.MODIFY.getUrl(),
AuthorizationPhaseType.EXECUTION, objectNew, change, null, ownerResolver, result);
metadataManager.applyMetadataModify(change, objectContext, objectTypeClass,
clock.currentTimeXMLGregorianCalendar(), task, context, result);
if (change.isEmpty()) {
// Nothing to do
return;
}
if (TaskType.class.isAssignableFrom(objectTypeClass)) {
taskManager.modifyTask(change.getOid(), change.getModifications(), result);
} else if (NodeType.class.isAssignableFrom(objectTypeClass)) {
throw new UnsupportedOperationException("NodeType is not modifiable using model interface");
} else if (ObjectTypes.isClassManagedByProvisioning(objectTypeClass)) {
ProvisioningOperationOptions provisioningOptions = getProvisioningOptions(context, options);
String oid = modifyProvisioningObject(objectTypeClass, change.getOid(),
change.getModifications(), context, objectContext, provisioningOptions, resource,
task, result);
if (!oid.equals(change.getOid())) {
change.setOid(oid);
}
} else {
FocusConstraintsChecker.clearCacheForDelta(change.getModifications());
cacheRepositoryService.modifyObject(objectTypeClass, change.getOid(),
change.getModifications(), result);
}
task.recordObjectActionExecuted(objectNew, objectTypeClass, change.getOid(), ChangeType.MODIFY,
context.getChannel(), null);
} catch (Throwable t) {
task.recordObjectActionExecuted(objectNew, objectTypeClass, change.getOid(), ChangeType.MODIFY,
context.getChannel(), t);
throw t;
}
}
private String addTask(TaskType task, OperationResult result)
throws ObjectAlreadyExistsException, ObjectNotFoundException {
try {
return taskManager.addTask(task.asPrismObject(), result);
} catch (ObjectAlreadyExistsException ex) {
throw ex;
} catch (Exception ex) {
LoggingUtils.logException(LOGGER, "Couldn't add object {} to task manager", ex, task.getName());
throw new SystemException(ex.getMessage(), ex);
}
}
private <F extends ObjectType, T extends ObjectType> String addProvisioningObject(PrismObject<T> object,
LensContext<F> context, LensElementContext<T> objectContext, ProvisioningOperationOptions options,
ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException,
ObjectAlreadyExistsException, SchemaException, CommunicationException,
ConfigurationException, SecurityViolationException, ExpressionEvaluationException {
if (object.canRepresent(ShadowType.class)) {
ShadowType shadow = (ShadowType) object.asObjectable();
String resourceOid = ShadowUtil.getResourceOid(shadow);
if (resourceOid == null) {
throw new IllegalArgumentException("Resource OID is null in shadow");
}
}
OperationProvisioningScriptsType scripts = null;
if (object.canRepresent(ShadowType.class)) {
scripts = prepareScripts(object, context, objectContext, ProvisioningOperationTypeType.ADD,
resource, task, result);
}
Utils.setRequestee(task, context);
String oid = provisioning.addObject(object, scripts, options, task, result);
Utils.clearRequestee(task);
return oid;
}
private <F extends ObjectType, T extends ObjectType> void deleteProvisioningObject(
Class<T> objectTypeClass, String oid, LensContext<F> context, LensElementContext<T> objectContext,
ProvisioningOperationOptions options, ResourceType resource, Task task, OperationResult result)
throws ObjectNotFoundException, ObjectAlreadyExistsException, SchemaException,
CommunicationException, ConfigurationException, SecurityViolationException,
ExpressionEvaluationException {
PrismObject<T> shadowToModify = null;
OperationProvisioningScriptsType scripts = null;
try {
GetOperationOptions rootOpts = GetOperationOptions.createNoFetch();
rootOpts.setPointInTimeType(PointInTimeType.FUTURE);
shadowToModify = provisioning.getObject(objectTypeClass, oid,
SelectorOptions.createCollection(rootOpts), task, result);
} catch (ObjectNotFoundException ex) {
// this is almost OK, mute the error and try to delete account (it
// will fail if something is wrong)
result.muteLastSubresultError();
}
if (ShadowType.class.isAssignableFrom(objectTypeClass)) {
scripts = prepareScripts(shadowToModify, context, objectContext,
ProvisioningOperationTypeType.DELETE, resource, task, result);
}
Utils.setRequestee(task, context);
provisioning.deleteObject(objectTypeClass, oid, options, scripts, task, result);
Utils.clearRequestee(task);
}
private <F extends ObjectType, T extends ObjectType> String modifyProvisioningObject(
Class<T> objectTypeClass, String oid, Collection<? extends ItemDelta> modifications,
LensContext<F> context, LensElementContext<T> objectContext, ProvisioningOperationOptions options,
ResourceType resource, Task task, OperationResult result) throws ObjectNotFoundException,
CommunicationException, SchemaException, ConfigurationException,
SecurityViolationException, ExpressionEvaluationException, ObjectAlreadyExistsException {
PrismObject<T> shadowToModify = null;
OperationProvisioningScriptsType scripts = null;
try {
GetOperationOptions rootOpts = GetOperationOptions.createNoFetch();
rootOpts.setPointInTimeType(PointInTimeType.FUTURE);
shadowToModify = provisioning.getObject(objectTypeClass, oid,
SelectorOptions.createCollection(rootOpts), task, result);
} catch (ObjectNotFoundException e) {
// We do not want the operation to fail here. The object might have
// been re-created on the resource
// or discovery might re-create it. So simply ignore this error and
// give provisioning a chance to fail
// properly.
result.muteLastSubresultError();
LOGGER.warn("Repository object {}: {} is gone. But trying to modify resource object anyway",
objectTypeClass, oid);
}
if (ShadowType.class.isAssignableFrom(objectTypeClass)) {
scripts = prepareScripts(shadowToModify, context, objectContext,
ProvisioningOperationTypeType.MODIFY, resource, task, result);
}
Utils.setRequestee(task, context);
String changedOid = provisioning.modifyObject(objectTypeClass, oid, modifications, scripts, options,
task, result);
Utils.clearRequestee(task);
return changedOid;
}
private <F extends ObjectType, T extends ObjectType> OperationProvisioningScriptsType prepareScripts(
PrismObject<T> changedObject, LensContext<F> context, LensElementContext<T> objectContext,
ProvisioningOperationTypeType operation, ResourceType resource, Task task, OperationResult result)
throws ObjectNotFoundException, SchemaException, CommunicationException,
ConfigurationException, SecurityViolationException, ExpressionEvaluationException {
if (resource == null) {
LOGGER.warn("Resource does not exist. Skipping processing scripts.");
return null;
}
OperationProvisioningScriptsType resourceScripts = resource.getScripts();
PrismObject<? extends ShadowType> resourceObject = (PrismObject<? extends ShadowType>) changedObject;
PrismObject<F> user = null;
if (context.getFocusContext() != null) {
if (context.getFocusContext().getObjectNew() != null) {
user = context.getFocusContext().getObjectNew();
} else if (context.getFocusContext().getObjectOld() != null) {
user = context.getFocusContext().getObjectOld();
}
}
ResourceShadowDiscriminator discr = ((LensProjectionContext) objectContext)
.getResourceShadowDiscriminator();
ExpressionVariables variables = Utils.getDefaultExpressionVariables(user, resourceObject, discr,
resource.asPrismObject(), context.getSystemConfiguration(), objectContext);
return evaluateScript(resourceScripts, discr, operation, null, variables, context, objectContext, task, result);
}
private OperationProvisioningScriptsType evaluateScript(OperationProvisioningScriptsType resourceScripts,
ResourceShadowDiscriminator discr, ProvisioningOperationTypeType operation, BeforeAfterType order,
ExpressionVariables variables, LensContext<?> context,
LensElementContext<?> objectContext, Task task,
OperationResult result)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {
OperationProvisioningScriptsType outScripts = new OperationProvisioningScriptsType();
if (resourceScripts != null) {
OperationProvisioningScriptsType scripts = resourceScripts.clone();
for (OperationProvisioningScriptType script : scripts.getScript()) {
if (!evaluateScriptCondition(script, variables, task, result)){
continue;
}
if (discr != null) {
if (script.getKind() != null && !script.getKind().isEmpty()
&& !script.getKind().contains(discr.getKind())) {
continue;
}
if (script.getIntent() != null && !script.getIntent().isEmpty()
&& !script.getIntent().contains(discr.getIntent()) && discr.getIntent() != null) {
continue;
}
}
if (script.getOperation().contains(operation)) {
if (order == null || order == script.getOrder()) {
for (ProvisioningScriptArgumentType argument : script.getArgument()) {
evaluateScriptArgument(argument, variables, context, objectContext, task, result);
}
outScripts.getScript().add(script);
}
}
}
}
return outScripts;
}
private boolean evaluateScriptCondition(OperationProvisioningScriptType script,
ExpressionVariables variables, Task task, OperationResult result) throws SchemaException, ExpressionEvaluationException, ObjectNotFoundException {
ExpressionType condition = script.getCondition();
if (condition == null) {
return true;
}
PrismPropertyValue<Boolean> conditionOutput = ExpressionUtil.evaluateCondition(variables, condition, expressionFactory, " condition for provisioning script ", task, result);
if (conditionOutput == null) {
return true;
}
Boolean conditionOutputValue = conditionOutput.getValue();
return BooleanUtils.isNotFalse(conditionOutputValue);
}
private void evaluateScriptArgument(ProvisioningScriptArgumentType argument,
ExpressionVariables variables, LensContext<?> context,
LensElementContext<?> objectContext, Task task,
OperationResult result)
throws SchemaException, ObjectNotFoundException, ExpressionEvaluationException {
QName FAKE_SCRIPT_ARGUMENT_NAME = new QName(SchemaConstants.NS_C, "arg");
PrismPropertyDefinition<String> scriptArgumentDefinition = new PrismPropertyDefinitionImpl<>(
FAKE_SCRIPT_ARGUMENT_NAME, DOMUtil.XSD_STRING, prismContext);
String shortDesc = "Provisioning script argument expression";
Expression<PrismPropertyValue<String>, PrismPropertyDefinition<String>> expression = expressionFactory
.makeExpression(argument, scriptArgumentDefinition, shortDesc, task, result);
ExpressionEvaluationContext params = new ExpressionEvaluationContext(null, variables, shortDesc, task,
result);
PrismValueDeltaSetTriple<PrismPropertyValue<String>> outputTriple = ModelExpressionThreadLocalHolder
.evaluateExpressionInContext(expression, params, context,
objectContext instanceof LensProjectionContext ? (LensProjectionContext) objectContext : null, task, result);
Collection<PrismPropertyValue<String>> nonNegativeValues = null;
if (outputTriple != null) {
nonNegativeValues = outputTriple.getNonNegativeValues();
}
// replace dynamic script with static value..
argument.getExpressionEvaluator().clear();
if (nonNegativeValues == null || nonNegativeValues.isEmpty()) {
// We need to create at least one evaluator. Otherwise the
// expression code will complain
// Element value = DOMUtil.createElement(SchemaConstants.C_VALUE);
// DOMUtil.setNill(value);
JAXBElement<RawType> el = new JAXBElement(SchemaConstants.C_VALUE, RawType.class,
new RawType(prismContext));
argument.getExpressionEvaluator().add(el);
} else {
for (PrismPropertyValue<String> val : nonNegativeValues) {
// Element value =
// DOMUtil.createElement(SchemaConstants.C_VALUE);
// value.setTextContent(val.getValue());
PrimitiveXNode<String> prim = new PrimitiveXNode<>();
prim.setValue(val.getValue(), DOMUtil.XSD_STRING);
JAXBElement<RawType> el = new JAXBElement(SchemaConstants.C_VALUE, RawType.class,
new RawType(prim, prismContext));
argument.getExpressionEvaluator().add(el);
}
}
}
private <T extends ObjectType, F extends ObjectType> void executeReconciliationScript(
LensProjectionContext projContext, LensContext<F> context, BeforeAfterType order, Task task,
OperationResult parentResult) throws SchemaException, ObjectNotFoundException,
ExpressionEvaluationException, CommunicationException, ConfigurationException,
SecurityViolationException, ObjectAlreadyExistsException {
if (!projContext.isDoReconciliation()) {
return;
}
ResourceType resource = projContext.getResource();
if (resource == null) {
LOGGER.warn("Resource does not exist. Skipping processing reconciliation scripts.");
return;
}
OperationProvisioningScriptsType resourceScripts = resource.getScripts();
if (resourceScripts == null) {
return;
}
PrismObject<F> user = null;
PrismObject<ShadowType> shadow = null;
if (context.getFocusContext() != null) {
if (context.getFocusContext().getObjectNew() != null) {
user = context.getFocusContext().getObjectNew();
} else if (context.getFocusContext().getObjectOld() != null) {
user = context.getFocusContext().getObjectOld();
}
// if (order == ProvisioningScriptOrderType.BEFORE) {
// user = context.getFocusContext().getObjectOld();
// } else if (order == ProvisioningScriptOrderType.AFTER) {
// user = context.getFocusContext().getObjectNew();
// } else {
// throw new IllegalArgumentException("Unknown order "+order);
// }
}
if (order == BeforeAfterType.BEFORE) {
shadow = (PrismObject<ShadowType>) projContext.getObjectOld();
} else if (order == BeforeAfterType.AFTER) {
shadow = (PrismObject<ShadowType>) projContext.getObjectNew();
} else {
throw new IllegalArgumentException("Unknown order " + order);
}
ExpressionVariables variables = Utils.getDefaultExpressionVariables(user, shadow,
projContext.getResourceShadowDiscriminator(), resource.asPrismObject(),
context.getSystemConfiguration(), projContext);
OperationProvisioningScriptsType evaluatedScript = evaluateScript(resourceScripts,
projContext.getResourceShadowDiscriminator(), ProvisioningOperationTypeType.RECONCILE, order,
variables, context, projContext, task, parentResult);
for (OperationProvisioningScriptType script : evaluatedScript.getScript()) {
Utils.setRequestee(task, context);
provisioning.executeScript(resource.getOid(), script, task, parentResult);
Utils.clearRequestee(task);
}
}
}