/*
* Copyright (c) 2010-2016 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 java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.evolveum.midpoint.prism.ConsistencyCheckScope;
import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.schema.DeltaConvertor;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.PolicyViolationException;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType;
import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import com.evolveum.midpoint.common.crypto.CryptoUtil;
import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRule;
import com.evolveum.midpoint.model.api.context.EvaluatedPolicyRuleTrigger;
import com.evolveum.midpoint.model.api.context.ModelElementContext;
import com.evolveum.midpoint.model.common.expression.ObjectDeltaObject;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismObjectDefinition;
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.schema.util.ShadowUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import org.jetbrains.annotations.NotNull;
/**
* @author semancik
*
*/
public abstract class LensElementContext<O extends ObjectType> implements ModelElementContext<O> {
private static final long serialVersionUID = 1649567559396392861L;
private static final Trace LOGGER = TraceManager.getTrace(LensElementContext.class);
private PrismObject<O> objectOld;
private transient PrismObject<O> objectCurrent;
private PrismObject<O> objectNew;
private ObjectDelta<O> primaryDelta;
@NotNull private final List<LensObjectDeltaOperation<O>> executedDeltas = new ArrayList<>();
private Class<O> objectTypeClass;
private String oid = null;
private int iteration;
private String iterationToken;
/**
* Initial intent regarding the account. It indicated what the initiator of the operation WANTS TO DO with the
* context.
* If set to null then the decision is left to "the engine". Null is also a typical value
* when the context is created. It may be pre-set under some circumstances, e.g. if an account is being unlinked.
*/
private SynchronizationIntent synchronizationIntent;
private transient boolean isFresh = false;
private LensContext<? extends ObjectType> lensContext;
private transient PrismObjectDefinition<O> objectDefinition = null;
transient private Collection<EvaluatedPolicyRule> policyRules = new ArrayList<>();
transient private Collection<String> policySituations = new ArrayList<>();
public LensElementContext(Class<O> objectTypeClass, LensContext<? extends ObjectType> lensContext) {
super();
Validate.notNull(objectTypeClass, "Object class is null");
Validate.notNull(lensContext, "Lens context is null");
this.lensContext = lensContext;
this.objectTypeClass = objectTypeClass;
}
public int getIteration() {
return iteration;
}
public void setIteration(int iteration) {
this.iteration = iteration;
}
public String getIterationToken() {
return iterationToken;
}
public void setIterationToken(String iterationToken) {
this.iterationToken = iterationToken;
}
public SynchronizationIntent getSynchronizationIntent() {
return synchronizationIntent;
}
public void setSynchronizationIntent(SynchronizationIntent synchronizationIntent) {
this.synchronizationIntent = synchronizationIntent;
}
public LensContext<? extends ObjectType> getLensContext() {
return lensContext;
}
protected PrismContext getNotNullPrismContext() {
return getLensContext().getNotNullPrismContext();
}
@Override
public Class<O> getObjectTypeClass() {
return objectTypeClass;
}
public boolean canRepresent(Class type) {
return type.isAssignableFrom(objectTypeClass);
}
public PrismContext getPrismContext() {
return lensContext.getPrismContext();
}
@Override
public PrismObject<O> getObjectOld() {
return objectOld;
}
public void setObjectOld(PrismObject<O> objectOld) {
this.objectOld = objectOld;
}
public PrismObject<O> getObjectCurrent() {
return objectCurrent;
}
public void setObjectCurrent(PrismObject<O> objectCurrent) {
this.objectCurrent = objectCurrent;
}
public PrismObject<O> getObjectAny() {
if (objectNew != null) {
return objectNew;
}
if (objectCurrent != null) {
return objectCurrent;
}
return objectOld;
}
/**
* Sets current and possibly also old object. This method is used with
* freshly loaded object. The object is set as current object.
* If the old object was not initialized yet (and if it should be initialized)
* then the object is also set as old object.
*/
public void setLoadedObject(PrismObject<O> object) {
setObjectCurrent(object);
if (objectOld == null && !isAdd()) {
setObjectOld(object.clone());
}
}
@Override
public PrismObject<O> getObjectNew() {
return objectNew;
}
public void setObjectNew(PrismObject<O> objectNew) {
this.objectNew = objectNew;
}
@Override
public ObjectDelta<O> getPrimaryDelta() {
return primaryDelta;
}
/**
* As getPrimaryDelta() but caters for the possibility that an object already exists.
* So, if the primary delta is ADD and object already exists, it should be changed somehow,
* e.g. to MODIFY delta or to null.
*
* Actually, the question is what to do with the attribute values if changed to MODIFY.
* (a) Should they become REPLACE item deltas? (b) ADD ones?
* (c) Or should we compute a difference from objectCurrent to objectToAdd, hoping that
* secondary deltas will re-add everything that might be unknowingly removed by this step?
* (d) Or should we simply ignore ADD delta altogether, hoping that it was executed
* so it need not be repeated?
*
* And, should not we report AlreadyExistingException instead?
*
* It seems that (c) i.e. reverting back to objectToAdd is not a good idea at all. For example, this
* may erase linkRefs for good.
*
* For the time being let us proceed with (d), i.e. ignoring such a delta.
*
* TODO is this OK???? [med]
*
* @return
*/
public ObjectDelta<O> getFixedPrimaryDelta() {
if (primaryDelta == null || !primaryDelta.isAdd() || objectCurrent == null) {
return primaryDelta; // nothing to do
}
// Object does exist. Let's ignore the delta - see description above.
return null;
}
public void setPrimaryDelta(ObjectDelta<O> primaryDelta) {
this.primaryDelta = primaryDelta;
}
public void addPrimaryDelta(ObjectDelta<O> delta) throws SchemaException {
if (primaryDelta == null) {
primaryDelta = delta;
} else {
primaryDelta.merge(delta);
}
}
public void swallowToPrimaryDelta(ItemDelta<?,?> itemDelta) throws SchemaException {
if (primaryDelta == null) {
primaryDelta = new ObjectDelta<O>(getObjectTypeClass(), ChangeType.MODIFY, getPrismContext());
primaryDelta.setOid(oid);
}
primaryDelta.swallow(itemDelta);
}
public abstract void swallowToSecondaryDelta(ItemDelta<?,?> itemDelta) throws SchemaException;
public boolean isAdd() {
if (ObjectDelta.isAdd(getPrimaryDelta())) {
return true;
}
if (ObjectDelta.isAdd(getSecondaryDelta())) {
return true;
}
return false;
}
public boolean isModify() {
if (ObjectDelta.isModify(getPrimaryDelta())) {
return true;
}
if (ObjectDelta.isModify(getSecondaryDelta())) {
return true;
}
return false;
}
public boolean isDelete() {
if (ObjectDelta.isDelete(getPrimaryDelta())) {
return true;
}
if (ObjectDelta.isDelete(getSecondaryDelta())) {
return true;
}
return false;
}
public SimpleOperationName getOperation() {
if (isAdd()) {
return SimpleOperationName.ADD;
}
if (isDelete()) {
return SimpleOperationName.DELETE;
}
return SimpleOperationName.MODIFY;
}
@NotNull
@Override
public List<LensObjectDeltaOperation<O>> getExecutedDeltas() {
return executedDeltas;
}
List<LensObjectDeltaOperation<O>> getExecutedDeltas(Boolean audited) {
if (audited == null) {
return executedDeltas;
}
List<LensObjectDeltaOperation<O>> deltas = new ArrayList<LensObjectDeltaOperation<O>>();
for (LensObjectDeltaOperation<O> delta: executedDeltas) {
if (delta.isAudited() == audited) {
deltas.add(delta);
}
}
return deltas;
}
public void markExecutedDeltasAudited() {
for(LensObjectDeltaOperation<O> executedDelta: executedDeltas) {
executedDelta.setAudited(true);
}
}
public void addToExecutedDeltas(LensObjectDeltaOperation<O> executedDelta) {
executedDeltas.add(executedDelta.clone()); // must be cloned because e.g. for ADD deltas the object gets modified afterwards
}
/**
* Returns user delta, both primary and secondary (merged together).
* The returned object is (kind of) immutable. Changing it may do strange things (but most likely the changes will be lost).
*/
public ObjectDelta<O> getDelta() throws SchemaException {
return ObjectDelta.union(primaryDelta, getSecondaryDelta());
}
public ObjectDelta<O> getFixedDelta() throws SchemaException {
return ObjectDelta.union(getFixedPrimaryDelta(), getSecondaryDelta());
}
public <F extends FocusType> boolean wasAddExecuted() {
for (LensObjectDeltaOperation<O> executedDeltaOperation : getExecutedDeltas()){
ObjectDelta<O> executedDelta = executedDeltaOperation.getObjectDelta();
if (!executedDelta.isAdd()){
continue;
} else if (executedDelta.getObjectToAdd() != null && executedDelta.getObjectTypeClass().equals(getObjectTypeClass())){
return true;
}
}
return false;
}
abstract public ObjectDeltaObject<O> getObjectDeltaObject() throws SchemaException;
@Override
public String getOid() {
if (oid == null) {
oid = determineOid();
}
return oid;
}
public String determineOid() {
if (getObjectOld() != null && getObjectOld().getOid() != null) {
return getObjectOld().getOid();
}
if (getObjectCurrent() != null && getObjectCurrent().getOid() != null) {
return getObjectCurrent().getOid();
}
if (getObjectNew() != null && getObjectNew().getOid() != null) {
return getObjectNew().getOid();
}
if (getPrimaryDelta() != null && getPrimaryDelta().getOid() != null) {
return getPrimaryDelta().getOid();
}
if (getSecondaryDelta() != null && getSecondaryDelta().getOid() != null) {
return getSecondaryDelta().getOid();
}
return null;
}
/**
* Sets oid to the field but also to the deltas (if applicable).
*/
public void setOid(String oid) {
this.oid = oid;
if (primaryDelta != null) {
primaryDelta.setOid(oid);
}
if (objectNew != null) {
objectNew.setOid(oid);
}
}
public PrismObjectDefinition<O> getObjectDefinition() {
if (objectDefinition == null) {
if (objectOld != null) {
objectDefinition = objectOld.getDefinition();
} else if (objectCurrent != null) {
objectDefinition = objectCurrent.getDefinition();
} else if (objectNew != null) {
objectDefinition = objectNew.getDefinition();
} else {
objectDefinition = getNotNullPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(getObjectTypeClass());
}
}
return objectDefinition;
}
public boolean isFresh() {
return isFresh;
}
public void setFresh(boolean isFresh) {
this.isFresh = isFresh;
}
public Collection<EvaluatedPolicyRule> getPolicyRules() {
return policyRules;
}
public void addPolicyRule(EvaluatedPolicyRule policyRule) {
this.policyRules.add(policyRule);
}
public void triggerConstraint(EvaluatedPolicyRule rule, EvaluatedPolicyRuleTrigger trigger) throws PolicyViolationException {
LensUtil.triggerConstraint(rule, trigger, policySituations);
}
public Collection<String> getPolicySituations() {
return policySituations;
}
public void recompute() throws SchemaException {
PrismObject<O> base = objectCurrent;
if (base == null) {
base = objectOld;
}
ObjectDelta<O> delta = getDelta();
if (delta == null) {
// No change
objectNew = base;
return;
}
objectNew = delta.computeChangedObject(base);
}
public void checkConsistence() {
checkConsistence(null);
}
public void checkConsistence(String contextDesc) {
if (getObjectOld() != null) {
checkConsistence(getObjectOld(), "old "+getElementDesc() , contextDesc);
}
if (getObjectCurrent() != null) {
checkConsistence(getObjectCurrent(), "current "+getElementDesc() , contextDesc);
}
if (primaryDelta != null) {
checkConsistence(primaryDelta, false, getElementDesc()+" primary delta in "+this + (contextDesc == null ? "" : " in " +contextDesc));
}
if (getObjectNew() != null) {
checkConsistence(getObjectNew(), "new "+getElementDesc(), contextDesc);
}
}
protected void checkConsistence(ObjectDelta<O> delta, boolean requireOid, String contextDesc) {
try {
delta.checkConsistence(requireOid, true, true, ConsistencyCheckScope.THOROUGH);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage()+"; in "+contextDesc, e);
} catch (IllegalStateException e) {
throw new IllegalStateException(e.getMessage()+"; in "+contextDesc, e);
}
if (delta.isAdd()) {
checkConsistence(delta.getObjectToAdd(), "add object", contextDesc);
}
}
protected boolean isRequireSecondardyDeltaOid() {
return primaryDelta == null;
}
protected void checkConsistence(PrismObject<O> object, String elementDesc, String contextDesc) {
String desc = elementDesc+" in "+this + (contextDesc == null ? "" : " in " +contextDesc);
try {
object.checkConsistence(true, ConsistencyCheckScope.THOROUGH);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e.getMessage()+"; in "+desc, e);
} catch (IllegalStateException e) {
throw new IllegalStateException(e.getMessage()+"; in "+desc, e);
}
if (object.getDefinition() == null) {
throw new IllegalStateException("No "+getElementDesc()+" definition "+desc);
}
O objectType = object.asObjectable();
if (objectType instanceof ShadowType) {
ShadowUtil.checkConsistence((PrismObject<? extends ShadowType>) object, desc);
}
}
/**
* Cleans up the contexts by removing some of the working state.
*/
public abstract void cleanup();
public void normalize() {
if (objectNew != null) {
objectNew.normalize();
}
if (objectOld != null) {
objectOld.normalize();
}
if (objectCurrent != null) {
objectCurrent.normalize();
}
if (primaryDelta != null) {
primaryDelta.normalize();
}
}
public void adopt(PrismContext prismContext) throws SchemaException {
if (objectNew != null) {
prismContext.adopt(objectNew);
}
if (objectOld != null) {
prismContext.adopt(objectOld);
}
if (objectCurrent != null) {
prismContext.adopt(objectCurrent);
}
if (primaryDelta != null) {
prismContext.adopt(primaryDelta);
}
// TODO: object definition?
}
public abstract LensElementContext<O> clone(LensContext<? extends ObjectType> lensContext);
protected void copyValues(LensElementContext<O> clone, LensContext lensContext) {
clone.lensContext = lensContext;
// This is de-facto immutable
clone.objectDefinition = this.objectDefinition;
clone.objectNew = cloneObject(this.objectNew);
clone.objectOld = cloneObject(this.objectOld);
clone.objectCurrent = cloneObject(this.objectCurrent);
clone.objectTypeClass = this.objectTypeClass;
clone.oid = this.oid;
clone.primaryDelta = cloneDelta(this.primaryDelta);
clone.isFresh = this.isFresh;
clone.iteration = this.iteration;
clone.iterationToken = this.iterationToken;
}
protected ObjectDelta<O> cloneDelta(ObjectDelta<O> thisDelta) {
if (thisDelta == null) {
return null;
}
return thisDelta.clone();
}
private PrismObject<O> cloneObject(PrismObject<O> thisObject) {
if (thisObject == null) {
return null;
}
return thisObject.clone();
}
public void storeIntoLensElementContextType(LensElementContextType lensElementContextType) throws SchemaException {
lensElementContextType.setObjectOld(objectOld != null ? objectOld.asObjectable() : null);
lensElementContextType.setObjectNew(objectNew != null ? objectNew.asObjectable() : null);
lensElementContextType.setPrimaryDelta(primaryDelta != null ? DeltaConvertor.toObjectDeltaType(primaryDelta) : null);
for (LensObjectDeltaOperation executedDelta : executedDeltas) {
lensElementContextType.getExecutedDeltas().add(executedDelta.toLensObjectDeltaOperationType());
}
lensElementContextType.setObjectTypeClass(objectTypeClass != null ? objectTypeClass.getName() : null);
lensElementContextType.setOid(oid);
lensElementContextType.setIteration(iteration);
lensElementContextType.setIterationToken(iterationToken);
lensElementContextType.setSynchronizationIntent(synchronizationIntent != null ? synchronizationIntent.toSynchronizationIntentType() : null);
}
public void retrieveFromLensElementContextType(LensElementContextType lensElementContextType, OperationResult result) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException {
ObjectType objectTypeOld = lensElementContextType.getObjectOld();
this.objectOld = objectTypeOld != null ? (PrismObject) objectTypeOld.asPrismObject() : null;
fixProvisioningTypeInObject(this.objectOld, result);
ObjectType objectTypeNew = lensElementContextType.getObjectNew();
this.objectNew = objectTypeNew != null ? (PrismObject) objectTypeNew.asPrismObject() : null;
fixProvisioningTypeInObject(this.objectNew, result);
ObjectType object = objectTypeNew != null ? objectTypeNew : objectTypeOld;
ObjectDeltaType primaryDeltaType = lensElementContextType.getPrimaryDelta();
this.primaryDelta = primaryDeltaType != null ? (ObjectDelta) DeltaConvertor.createObjectDelta(primaryDeltaType, lensContext.getPrismContext()) : null;
fixProvisioningTypeInDelta(this.primaryDelta, object, result);
for (LensObjectDeltaOperationType eDeltaOperationType : lensElementContextType.getExecutedDeltas()) {
LensObjectDeltaOperation objectDeltaOperation = LensObjectDeltaOperation.fromLensObjectDeltaOperationType(eDeltaOperationType, lensContext.getPrismContext());
if (objectDeltaOperation.getObjectDelta() != null) {
fixProvisioningTypeInDelta(objectDeltaOperation.getObjectDelta(), object, result);
}
this.executedDeltas.add(objectDeltaOperation);
}
this.oid = lensElementContextType.getOid();
this.iteration = lensElementContextType.getIteration() != null ? lensElementContextType.getIteration() : 0;
this.iterationToken = lensElementContextType.getIterationToken();
this.synchronizationIntent = SynchronizationIntent.fromSynchronizationIntentType(lensElementContextType.getSynchronizationIntent());
// note: objectTypeClass is already converted (used in the constructor)
}
protected void fixProvisioningTypeInDelta(ObjectDelta<O> delta, Objectable object, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException {
if (delta != null && delta.getObjectTypeClass() != null && (ShadowType.class.isAssignableFrom(delta.getObjectTypeClass()) || ResourceType.class.isAssignableFrom(delta.getObjectTypeClass()))) {
lensContext.getProvisioningService().applyDefinition(delta, object, result);
}
}
private void fixProvisioningTypeInObject(PrismObject<O> object, OperationResult result) throws SchemaException, ObjectNotFoundException, CommunicationException, ConfigurationException {
if (object != null && object.getCompileTimeClass() != null && (ShadowType.class.isAssignableFrom(object.getCompileTimeClass()) || ResourceType.class.isAssignableFrom(object.getCompileTimeClass()))) {
lensContext.getProvisioningService().applyDefinition(object, result);
}
}
public void checkEncrypted() {
if (objectNew != null) {
CryptoUtil.checkEncrypted(objectNew);
}
if (objectOld != null) {
CryptoUtil.checkEncrypted(objectOld);
}
if (objectCurrent != null) {
CryptoUtil.checkEncrypted(objectCurrent);
}
if (primaryDelta != null) {
CryptoUtil.checkEncrypted(primaryDelta);
}
}
public boolean operationMatches(ChangeTypeType operation) {
switch (operation) {
case ADD:
return getOperation() == SimpleOperationName.ADD;
case MODIFY:
return getOperation() == SimpleOperationName.MODIFY;
case DELETE:
return getOperation() == SimpleOperationName.DELETE;
}
throw new IllegalArgumentException("Unknown operaiton "+operation);
}
protected abstract String getElementDefaultDesc();
protected String getElementDesc() {
PrismObject<O> object = getObjectNew();
if (object == null) {
object = getObjectOld();
}
if (object == null) {
object = getObjectCurrent();
}
if (object == null) {
return getElementDefaultDesc();
}
return object.toDebugType();
}
protected String getDebugDumpTitle() {
return StringUtils.capitalize(getElementDesc());
}
protected String getDebugDumpTitle(String suffix) {
return getDebugDumpTitle()+" "+suffix;
}
public abstract String getHumanReadableName();
}