/*
* 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.prism.delta;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPath.CompareResult;
import com.evolveum.midpoint.prism.util.PrismUtil;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import javax.xml.namespace.QName;
import com.evolveum.prism.xml.ns._public.types_3.ChangeTypeType;
import com.evolveum.prism.xml.ns._public.types_3.ItemDeltaType;
import com.evolveum.prism.xml.ns._public.types_3.ObjectDeltaType;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.Validate;
import org.jetbrains.annotations.NotNull;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;
/**
* Relative difference (delta) of the object.
* <p/>
* This class describes how the object changes. It can describe either object addition, modification of deletion.
* <p/>
* Addition described complete new (absolute) state of the object.
* <p/>
* Modification contains a set property deltas that describe relative changes to individual properties
* <p/>
* Deletion does not contain anything. It only marks object for deletion.
* <p/>
* The OID is mandatory for modification and deletion.
*
* @author Radovan Semancik
* @see PropertyDelta
*/
public class ObjectDelta<T extends Objectable> implements DebugDumpable, Visitable, PathVisitable, Serializable {
private static final long serialVersionUID = -528560467958335366L;
private ChangeType changeType;
/**
* OID of the object that this delta applies to.
*/
private String oid;
/**
* New object to add. Valid only if changeType==ADD
*/
private PrismObject<T> objectToAdd;
/**
* Set of relative property deltas. Valid only if changeType==MODIFY
*/
@NotNull private final Collection<? extends ItemDelta<?,?>> modifications;
/**
* Class of the object that we describe.
*/
private Class<T> objectTypeClass;
transient private PrismContext prismContext;
public ObjectDelta(Class<T> objectTypeClass, ChangeType changeType, PrismContext prismContext) {
Validate.notNull(objectTypeClass,"No objectTypeClass");
Validate.notNull(changeType,"No changeType");
//Validate.notNull(prismContext, "No prismContext");
this.changeType = changeType;
this.objectTypeClass = objectTypeClass;
this.prismContext = prismContext;
objectToAdd = null;
modifications = createEmptyModifications();
}
@Override
public void accept(Visitor visitor) {
accept(visitor, true);
}
public void accept(Visitor visitor, boolean includeOldValues) {
visitor.visit(this);
if (isAdd()) {
objectToAdd.accept(visitor);
} else if (isModify()) {
for (ItemDelta<?,?> delta : getModifications()){
delta.accept(visitor, includeOldValues);
}
}
// Nothing to visit for delete
}
@Override
public void accept(Visitor visitor, ItemPath path, boolean recursive) {
if (path == null || path.isEmpty()) {
if (recursive) {
accept(visitor);
} else {
visitor.visit(this);
}
} else {
ItemDelta.accept(getModifications(), visitor, path, recursive);
}
}
public ChangeType getChangeType() {
return changeType;
}
public void setChangeType(ChangeType changeType) {
this.changeType = changeType;
}
public static boolean isAdd(ObjectDelta<?> objectDelta) {
if (objectDelta == null) {
return false;
}
return objectDelta.isAdd();
}
public boolean isAdd() {
return changeType == ChangeType.ADD;
}
public static boolean isDelete(ObjectDelta<?> objectDelta) {
if (objectDelta == null) {
return false;
}
return objectDelta.isDelete();
}
public boolean isDelete() {
return changeType == ChangeType.DELETE;
}
public static boolean isModify(ObjectDelta<?> objectDelta) {
if (objectDelta == null) {
return false;
}
return objectDelta.isModify();
}
public boolean isModify() {
return changeType == ChangeType.MODIFY;
}
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
if (objectToAdd != null) {
objectToAdd.setOid(oid);
}
}
public PrismContext getPrismContext() {
return prismContext;
}
public void setPrismContext(PrismContext prismContext) {
this.prismContext = prismContext;
}
public PrismObject<T> getObjectToAdd() {
return objectToAdd;
}
public void setObjectToAdd(PrismObject<T> objectToAdd) {
if (getChangeType() != ChangeType.ADD) {
throw new IllegalStateException("Cannot set object to "+getChangeType()+" delta");
}
this.objectToAdd = objectToAdd;
if (objectToAdd != null) {
this.objectTypeClass = objectToAdd.getCompileTimeClass();
}
}
public Collection<? extends ItemDelta<?,?>> getModifications() {
return modifications;
}
/**
* Adds modification (itemDelta) and returns the modification that was added.
* NOTE: the modification that was added may be different from the modification that was
* passed into this method! E.g. in case if two modifications must be merged to keep the delta
* consistent. Therefore always use the returned modification after this method is invoked.
*/
public <D extends ItemDelta> D addModification(D itemDelta) {
if (getChangeType() != ChangeType.MODIFY) {
throw new IllegalStateException("Cannot add modifications to "+getChangeType()+" delta");
}
ItemPath itemPath = itemDelta.getPath();
D existingModification = (D) findModification(itemPath, itemDelta.getClass());
if (existingModification != null) {
existingModification.merge(itemDelta);
return existingModification;
} else {
((Collection)modifications).add(itemDelta);
return itemDelta;
}
}
public boolean containsModification(ItemDelta itemDelta) {
return containsModification(itemDelta, PrismConstants.EQUALS_DEFAULT_IGNORE_METADATA, PrismConstants.EQUALS_DEFAULT_IS_LITERAL);
}
public boolean containsModification(ItemDelta itemDelta, boolean ignoreMetadata, boolean isLiteral) {
for (ItemDelta modification: modifications) {
if (modification.contains(itemDelta, ignoreMetadata, isLiteral)) {
return true;
}
}
return false;
}
public void addModifications(Collection<? extends ItemDelta> itemDeltas) {
for (ItemDelta<?,?> modDelta: itemDeltas) {
addModification(modDelta);
}
}
public void addModifications(ItemDelta<?,?>... itemDeltas) {
for (ItemDelta<?,?> modDelta: itemDeltas) {
addModification(modDelta);
}
}
public <IV extends PrismValue,ID extends ItemDefinition> ItemDelta<IV,ID> findItemDelta(ItemPath propertyPath) {
return findItemDelta(propertyPath, ItemDelta.class, Item.class);
}
private <IV extends PrismValue,ID extends ItemDefinition, I extends Item<IV,ID>,DD extends ItemDelta<IV,ID>>
DD findItemDelta(ItemPath propertyPath, Class<DD> deltaType, Class<I> itemType) {
if (changeType == ChangeType.ADD) {
I item = objectToAdd.findItem(propertyPath, itemType);
if (item == null) {
return null;
}
DD itemDelta = createEmptyDelta(propertyPath, item.getDefinition(), deltaType, item.getClass());
itemDelta.addValuesToAdd(item.getClonedValues());
return itemDelta;
} else if (changeType == ChangeType.MODIFY) {
return findModification(propertyPath, deltaType);
} else {
return null;
}
}
public <IV extends PrismValue,ID extends ItemDefinition> Collection<PartiallyResolvedDelta<IV,ID>> findPartial(ItemPath propertyPath) {
if (changeType == ChangeType.ADD) {
PartiallyResolvedItem<IV,ID> partialValue = objectToAdd.findPartial(propertyPath);
if (partialValue == null || partialValue.getItem() == null) {
return new ArrayList<>(0);
}
Item<IV,ID> item = partialValue.getItem();
ItemDelta<IV,ID> itemDelta = item.createDelta();
itemDelta.addValuesToAdd(item.getClonedValues());
Collection<PartiallyResolvedDelta<IV,ID>> deltas = new ArrayList<>(1);
deltas.add(new PartiallyResolvedDelta<IV, ID>(itemDelta, partialValue.getResidualPath()));
return deltas;
} else if (changeType == ChangeType.MODIFY) {
Collection<PartiallyResolvedDelta<IV,ID>> deltas = new ArrayList<>();
for (ItemDelta<?,?> modification: modifications) {
CompareResult compareComplex = modification.getPath().compareComplex(propertyPath);
if (compareComplex == CompareResult.EQUIVALENT) {
deltas.add(new PartiallyResolvedDelta<IV,ID>((ItemDelta<IV,ID>)modification, null));
} else if (compareComplex == CompareResult.SUBPATH) {
deltas.add(new PartiallyResolvedDelta<IV,ID>((ItemDelta<IV,ID>)modification, null));
} else if (compareComplex == CompareResult.SUPERPATH) {
deltas.add(new PartiallyResolvedDelta<IV,ID>((ItemDelta<IV,ID>)modification,
modification.getPath().remainder(propertyPath)));
}
}
return deltas;
} else {
return new ArrayList<>(0);
}
}
public boolean hasItemDelta(ItemPath propertyPath) {
if (changeType == ChangeType.ADD) {
Item item = objectToAdd.findItem(propertyPath, Item.class);
return item != null;
} else if (changeType == ChangeType.MODIFY) {
ItemDelta modification = findModification(propertyPath, ItemDelta.class);
return modification != null;
} else {
return false;
}
}
public boolean hasCompleteDefinition() {
if (isAdd()) {
return getObjectToAdd().hasCompleteDefinition();
} else if (isModify()) {
for (ItemDelta modification: getModifications()) {
if (!modification.hasCompleteDefinition()) {
return false;
}
// return true;
}
return true;
} else if (isDelete()) {
return true;
}
throw new IllegalStateException("Strange things happen");
}
private <D extends ItemDelta, I extends Item> D createEmptyDelta(ItemPath propertyPath, ItemDefinition itemDef,
Class<D> deltaType, Class<I> itemType) {
if (PrismProperty.class.isAssignableFrom(itemType)) {
return (D) new PropertyDelta(propertyPath, (PrismPropertyDefinition)itemDef, prismContext);
} else if (PrismContainer.class.isAssignableFrom(itemType)) {
return (D) new ContainerDelta(propertyPath, (PrismContainerDefinition)itemDef, prismContext);
} else if (PrismReference.class.isAssignableFrom(itemType)) {
return (D) new ReferenceDelta(propertyPath, (PrismReferenceDefinition)itemDef, prismContext);
} else {
throw new IllegalArgumentException("Unknown item type "+itemType);
}
}
public Class<T> getObjectTypeClass() {
return objectTypeClass;
}
public void setObjectTypeClass(Class<T> objectTypeClass) {
this.objectTypeClass = objectTypeClass;
}
/**
* Top-level path is assumed.
*/
public <X> PropertyDelta<X> findPropertyDelta(QName propertyName) {
return findPropertyDelta(new ItemPath(propertyName));
}
public <X> PropertyDelta<X> findPropertyDelta(ItemPath parentPath, QName propertyName) {
return findPropertyDelta(new ItemPath(parentPath, propertyName));
}
@SuppressWarnings("unchecked")
public <X> PropertyDelta<X> findPropertyDelta(ItemPath propertyPath) {
return findItemDelta(propertyPath, PropertyDelta.class, PrismProperty.class);
}
@SuppressWarnings("unchecked")
public <X extends Containerable> ContainerDelta<X> findContainerDelta(ItemPath propertyPath) {
return findItemDelta(propertyPath, ContainerDelta.class, PrismContainer.class);
}
public <X extends Containerable> ContainerDelta<X> findContainerDelta(QName name) {
return findContainerDelta(new ItemPath(name));
}
private <D extends ItemDelta> D findModification(ItemPath propertyPath, Class<D> deltaType) {
return ItemDelta.findItemDelta(modifications, propertyPath, deltaType);
}
private <D extends ItemDelta> D findModification(QName itemName, Class<D> deltaType) {
return findModification(new ItemPath(itemName), deltaType);
}
public ReferenceDelta findReferenceModification(QName itemName) {
return findModification(itemName, ReferenceDelta.class);
}
public ReferenceDelta findReferenceModification(ItemPath itemPath) {
return findModification(itemPath, ReferenceDelta.class);
}
/**
* Returns all item deltas at or below a specified path.
*/
public Collection<? extends ItemDelta<?,?>> findItemDeltasSubPath(ItemPath itemPath) {
return ItemDelta.findItemDeltasSubPath(modifications, itemPath);
}
private <D extends ItemDelta> void removeModification(ItemPath propertyPath, Class<D> deltaType) {
ItemDelta.removeItemDelta(modifications, propertyPath, deltaType);
}
public <D extends ItemDelta> void removeModification(ItemDelta<?,?> itemDelta) {
ItemDelta.removeItemDelta(modifications, itemDelta);
}
private <D extends ItemDelta> void removeModification(QName itemName, Class<D> deltaType) {
removeModification(new ItemPath(itemName), deltaType);
}
public void removeReferenceModification(QName itemName) {
removeModification(itemName, ReferenceDelta.class);
}
public void removeReferenceModification(ItemPath itemPath) {
removeModification(itemPath, ReferenceDelta.class);
}
public void removeContainerModification(QName itemName) {
removeModification(itemName, ContainerDelta.class);
}
public void removePropertyModification(QName itemName) {
removeModification(itemName, PropertyDelta.class);
}
public void removePropertyModification(ItemPath itemPath) {
removeModification(itemPath, PropertyDelta.class);
}
public boolean isEmpty() {
if (getChangeType() == ChangeType.DELETE) {
// Delete delta is never empty
return false;
}
if (getChangeType() == ChangeType.ADD) {
return objectToAdd == null || objectToAdd.isEmpty();
}
if (modifications == null || modifications.isEmpty()) {
return true;
}
for (ItemDelta<?,?> mod: modifications) {
if (!mod.isEmpty()) {
return false;
}
}
return true;
}
public static boolean isEmpty(ObjectDeltaType deltaType) {
if (deltaType == null) {
return true;
}
if (deltaType.getChangeType() == ChangeTypeType.DELETE) {
return false;
} else if (deltaType.getChangeType() == ChangeTypeType.ADD) {
return deltaType.getObjectToAdd() == null || deltaType.getObjectToAdd().asPrismObject().isEmpty();
} else {
for (ItemDeltaType itemDeltaType : deltaType.getItemDelta()) {
if (!ItemDelta.isEmpty(itemDeltaType)) {
return false;
}
}
return true;
}
}
public void normalize() {
if (objectToAdd != null) {
objectToAdd.normalize();
}
if (modifications != null) {
Iterator<? extends ItemDelta> iterator = modifications.iterator();
while (iterator.hasNext()) {
ItemDelta<?,?> modification = iterator.next();
modification.normalize();
if (modification.isEmpty()) {
iterator.remove();
}
}
}
}
public void applyDefinition(PrismObjectDefinition<T> definition) throws SchemaException {
if (objectToAdd != null) {
objectToAdd.applyDefinition(definition);
}
ItemDelta.applyDefinition(getModifications(), definition);
}
/**
* Deep clone.
*/
public ObjectDelta<T> clone() {
ObjectDelta<T> clone = new ObjectDelta<T>(this.objectTypeClass, this.changeType, this.prismContext);
clone.oid = this.oid;
for (ItemDelta<?,?> thisModification: this.modifications) {
((Collection)clone.modifications).add(thisModification.clone());
}
if (this.objectToAdd == null) {
clone.objectToAdd = null;
} else {
clone.objectToAdd = this.objectToAdd.clone();
}
return clone;
}
/**
* Merge provided delta into this delta.
* This delta is assumed to be chronologically earlier, delta in the parameter is assumed to come chronologicaly later.
*/
public void merge(ObjectDelta<T> deltaToMerge) throws SchemaException {
if (deltaToMerge == null) {
return;
}
if (changeType == ChangeType.ADD) {
if (deltaToMerge.changeType == ChangeType.ADD) {
// Maybe we can, be we do not want. This is usually an error anyway.
throw new IllegalArgumentException("Cannot merge two ADD deltas: " + this + ", " + deltaToMerge);
} else if (deltaToMerge.changeType == ChangeType.MODIFY) {
if (objectToAdd == null) {
throw new IllegalStateException("objectToAdd is null");
}
deltaToMerge.applyTo(objectToAdd);
} else if (deltaToMerge.changeType == ChangeType.DELETE) {
this.changeType = ChangeType.DELETE;
}
} else if (changeType == ChangeType.MODIFY) {
if (deltaToMerge.changeType == ChangeType.ADD) {
throw new IllegalArgumentException("Cannot merge 'add' delta to a 'modify' object delta");
} else if (deltaToMerge.changeType == ChangeType.MODIFY) {
mergeModifications(deltaToMerge.modifications);
} else if (deltaToMerge.changeType == ChangeType.DELETE) {
this.changeType = ChangeType.DELETE;
}
} else { // DELETE
if (deltaToMerge.changeType == ChangeType.ADD) {
this.changeType = ChangeType.ADD;
// TODO: clone?
this.objectToAdd = deltaToMerge.objectToAdd;
} else if (deltaToMerge.changeType == ChangeType.MODIFY) {
// Just ignore the modification of a deleted object
} else if (deltaToMerge.changeType == ChangeType.DELETE) {
// Nothing to do
}
}
}
/**
* Returns a delta that is a "sum" of all the deltas in the collection.
* The deltas as processed as an ORDERED sequence. Therefore it correctly processes item overwrites and so on.
* It also means that if there is an ADD delta it has to be first.
*/
@SafeVarargs
public static <T extends Objectable> ObjectDelta<T> summarize(ObjectDelta<T>... deltas) throws SchemaException {
return summarize(Arrays.asList(deltas));
}
/**
* Returns a delta that is a "sum" of all the deltas in the collection.
* The deltas as processed as an ORDERED sequence. Therefore it correctly processes item overwrites and so on.
* It also means that if there is an ADD delta it has to be first.
*/
public static <T extends Objectable> ObjectDelta<T> summarize(List<ObjectDelta<T>> deltas) throws SchemaException {
if (deltas == null || deltas.isEmpty()) {
return null;
}
Iterator<ObjectDelta<T>> iterator = deltas.iterator();
ObjectDelta<T> sumDelta = iterator.next().clone();
while (iterator.hasNext()) {
ObjectDelta<T> nextDelta = iterator.next();
sumDelta.merge(nextDelta);
}
return sumDelta;
}
/**
* Union of several object deltas. The deltas are merged to create a single delta
* that contains changes from all the deltas.
*
* Union works on UNORDERED deltas.
*/
public static <T extends Objectable> ObjectDelta<T> union(ObjectDelta<T>... deltas) throws SchemaException {
List<ObjectDelta<T>> modifyDeltas = new ArrayList<>(deltas.length);
ObjectDelta<T> addDelta = null;
ObjectDelta<T> deleteDelta = null;
for (ObjectDelta<T> delta : deltas) {
if (delta == null) {
continue;
}
if (delta.changeType == ChangeType.MODIFY) {
modifyDeltas.add(delta);
} else if (delta.changeType == ChangeType.ADD) {
if (addDelta != null) {
// Maybe we can, be we do not want. This is usually an error anyway.
throw new IllegalArgumentException("Cannot merge two add deltas: " + addDelta + ", " + delta);
}
addDelta = delta;
} else if (delta.changeType == ChangeType.DELETE) {
deleteDelta = delta;
}
}
if (deleteDelta != null && addDelta == null) {
// Merging DELETE with anything except ADD is still a DELETE
return deleteDelta.clone();
}
if (deleteDelta != null && addDelta != null) {
throw new IllegalArgumentException("Cannot merge add and delete deltas: " + addDelta + ", " + deleteDelta);
}
if (addDelta != null) {
return mergeToDelta(addDelta, modifyDeltas);
} else {
if (modifyDeltas.size() == 0) {
return null;
}
if (modifyDeltas.size() == 1) {
return modifyDeltas.get(0);
}
return mergeToDelta(modifyDeltas.get(0), modifyDeltas.subList(1, modifyDeltas.size()));
}
}
private static <T extends Objectable> ObjectDelta<T> mergeToDelta(ObjectDelta<T> firstDelta,
List<ObjectDelta<T>> modifyDeltas) throws SchemaException {
if (modifyDeltas.size() == 0) {
return firstDelta;
}
ObjectDelta<T> delta = firstDelta.clone();
for (ObjectDelta<T> modifyDelta : modifyDeltas) {
if (modifyDelta == null) {
continue;
}
if (modifyDelta.changeType != ChangeType.MODIFY) {
throw new IllegalArgumentException("Can only merge MODIFY changes, got " + modifyDelta.changeType);
}
delta.mergeModifications(modifyDelta.modifications);
}
return delta;
}
public void mergeModifications(Collection<? extends ItemDelta> modificationsToMerge) throws SchemaException {
for (ItemDelta<?,?> propDelta : modificationsToMerge) {
mergeModification(propDelta);
}
}
public void mergeModification(ItemDelta<?,?> modificationToMerge) throws SchemaException {
if (changeType == ChangeType.ADD) {
modificationToMerge.applyTo(objectToAdd);
} else if (changeType == ChangeType.MODIFY) {
ItemDelta myDelta = findModification(modificationToMerge.getPath(), ItemDelta.class);
if (myDelta == null) {
addModification(modificationToMerge.clone());
} else {
myDelta.merge(modificationToMerge);
}
} // else it is DELETE. There's nothing to do. Merging anything to delete is still delete
}
/**
* Applies this object delta to specified object, returns updated object.
* It modifies the provided object.
*/
public void applyTo(PrismObject<T> targetObject) throws SchemaException {
if (isEmpty()) {
// nothing to do
return;
}
if (changeType != ChangeType.MODIFY) {
throw new IllegalStateException("Can apply only MODIFY delta to object, got " + changeType + " delta");
}
for (ItemDelta itemDelta : modifications) {
itemDelta.applyTo(targetObject);
}
}
/**
* Applies this object delta to specified object, returns updated object.
* It leaves the original object unchanged.
*
* @param objectOld object before change
* @return object with applied changes or null if the object should not exit (was deleted)
*/
public PrismObject<T> computeChangedObject(PrismObject<T> objectOld) throws SchemaException {
if (objectOld == null) {
if (getChangeType() == ChangeType.ADD) {
objectOld = getObjectToAdd();
return objectOld.clone();
} else {
//throw new IllegalStateException("Cannot apply "+getChangeType()+" delta to a null old object");
// This seems to be quite OK
return null;
}
}
if (getChangeType() == ChangeType.DELETE) {
return null;
}
// MODIFY change
PrismObject<T> objectNew = objectOld.clone();
for (ItemDelta modification : modifications) {
modification.applyTo(objectNew);
}
return objectNew;
}
/**
* Incorporates the property delta into the existing property deltas
* (regardless of the change type).
*/
public void swallow(ItemDelta<?,?> newItemDelta) throws SchemaException {
if (changeType == ChangeType.MODIFY) {
// TODO: check for conflict
addModification(newItemDelta);
} else if (changeType == ChangeType.ADD) {
Item item = objectToAdd.findOrCreateItem(newItemDelta.getPath(), newItemDelta.getItemClass());
newItemDelta.applyTo(item);
}
// nothing to do for DELETE
}
public void swallow(List<ItemDelta<?, ?>> itemDeltas) throws SchemaException {
for (ItemDelta<?, ?> itemDelta : itemDeltas) {
swallow(itemDelta);
}
}
private Collection<? extends ItemDelta<?,?>> createEmptyModifications() {
// Lists are easier to debug
return new ArrayList<>();
}
public <X> PropertyDelta<X> createPropertyModification(QName name, PrismPropertyDefinition<X> propertyDefinition) {
PropertyDelta<X> propertyDelta = new PropertyDelta<X>(name, propertyDefinition, prismContext);
return addModification(propertyDelta);
}
public <X> PropertyDelta<X> createPropertyModification(ItemPath path) {
PrismObjectDefinition<T> objDef = getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(getObjectTypeClass());
PrismPropertyDefinition<X> propDef = objDef.findPropertyDefinition(path);
return createPropertyModification(path, propDef);
}
public <C> PropertyDelta<C> createPropertyModification(ItemPath path, PrismPropertyDefinition propertyDefinition) {
PropertyDelta<C> propertyDelta = new PropertyDelta<C>(path, propertyDefinition, prismContext);
// No point in adding the modification to this delta. It will get merged anyway and it may disappear
// it is not reliable and therefore it is better not to add it now.
return propertyDelta;
}
public ReferenceDelta createReferenceModification(QName name, PrismReferenceDefinition referenceDefinition) {
ReferenceDelta referenceDelta = new ReferenceDelta(name, referenceDefinition, prismContext);
return addModification(referenceDelta);
}
public ReferenceDelta createReferenceModification(ItemPath path, PrismReferenceDefinition referenceDefinition) {
ReferenceDelta referenceDelta = new ReferenceDelta(path, referenceDefinition, prismContext);
return addModification(referenceDelta);
}
public <C extends Containerable> ContainerDelta<C> createContainerModification(QName qname) {
return createContainerModification(new ItemPath(qname));
}
public <C extends Containerable> ContainerDelta<C> createContainerModification(ItemPath path) {
PrismObjectDefinition<T> objDef = getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(getObjectTypeClass());
PrismContainerDefinition<C> propDef = objDef.findContainerDefinition(path);
return createContainerModification(path, propDef);
}
public <C extends Containerable> ContainerDelta<C> createContainerModification(ItemPath path, PrismContainerDefinition<C> containerDefinition) {
ContainerDelta<C> containerDelta = new ContainerDelta<C>(path, containerDefinition, prismContext);
return addModification(containerDelta);
}
/**
* Convenience method for quick creation of object deltas that replace a single object property. This is used quite often
* to justify a separate method.
*/
public static <O extends Objectable, X> ObjectDelta<O> createModificationReplaceProperty(Class<O> type, String oid, QName propertyName,
PrismContext prismContext, X... propertyValues) {
ItemPath propertyPath = new ItemPath(propertyName);
return createModificationReplaceProperty(type, oid, propertyPath, prismContext, propertyValues);
}
/**
* Convenience method for quick creation of object deltas that replace a single object property. This is used quite often
* to justify a separate method.
*/
public static <O extends Objectable, X> ObjectDelta<O> createModificationReplaceProperty(Class<O> type, String oid,
ItemPath propertyPath, PrismContext prismContext, X... propertyValues) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
fillInModificationReplaceProperty(objectDelta, propertyPath, propertyValues);
return objectDelta;
}
public static <O extends Objectable, X> ObjectDelta<O> createModificationAddProperty(Class<O> type, String oid,
QName propertyName, PrismContext prismContext, X... propertyValues) {
return createModificationAddProperty(type, oid, new ItemPath(propertyName), prismContext, propertyValues);
}
public static <O extends Objectable, X> ObjectDelta<O> createModificationAddProperty(Class<O> type, String oid,
ItemPath propertyPath, PrismContext prismContext, X... propertyValues) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
fillInModificationAddProperty(objectDelta, propertyPath, propertyValues);
return objectDelta;
}
public static <O extends Objectable, X> ObjectDelta<O> createModificationDeleteProperty(Class<O> type, String oid,
QName propertyName, PrismContext prismContext, X... propertyValues) {
return createModificationDeleteProperty(type, oid, new ItemPath(propertyName), prismContext, propertyValues);
}
public static <O extends Objectable, X> ObjectDelta<O> createModificationDeleteProperty(Class<O> type, String oid,
ItemPath propertyPath, PrismContext prismContext, X... propertyValues) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
fillInModificationDeleteProperty(objectDelta, propertyPath, propertyValues);
return objectDelta;
}
/**
* Convenience method for quick creation of object deltas that replace a single object reference.
*/
public static <O extends Objectable, X> ObjectDelta<O> createModificationReplaceReference(Class<O> type, String oid, QName referenceName,
PrismContext prismContext, PrismReferenceValue... refValues) {
ItemPath propertyPath = new ItemPath(referenceName);
return createModificationReplaceReference(type, oid, propertyPath, prismContext, refValues);
}
/**
* Convenience method for quick creation of object deltas that replace a single object reference.
*/
public static <O extends Objectable, X> ObjectDelta<O> createModificationReplaceReference(Class<O> type, String oid,
ItemPath refPath, PrismContext prismContext, PrismReferenceValue... refValues) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
fillInModificationReplaceReference(objectDelta, refPath, refValues);
return objectDelta;
}
public <X> void addModificationReplaceProperty(QName propertyQName, X... propertyValues) {
addModificationReplaceProperty(new ItemPath(propertyQName), propertyValues);
}
public <X> void addModificationReplaceProperty(ItemPath propertyPath, X... propertyValues) {
fillInModificationReplaceProperty(this, propertyPath, propertyValues);
}
public <X> void addModificationReplaceReference(ItemPath refPath, PrismReferenceValue... refValues) {
fillInModificationReplaceReference(this, refPath, refValues);
}
public <X> void addModificationAddProperty(QName propertyQName, X... propertyValues) {
addModificationAddProperty(new ItemPath(propertyQName), propertyValues);
}
public <X> void addModificationAddProperty(ItemPath propertyPath, X... propertyValues) {
fillInModificationAddProperty(this, propertyPath, propertyValues);
}
public <X> void addModificationDeleteProperty(QName propertyQName, X... propertyValues) {
addModificationDeleteProperty(new ItemPath(propertyQName), propertyValues);
}
public <X> void addModificationDeleteProperty(ItemPath propertyPath, X... propertyValues) {
fillInModificationDeleteProperty(this, propertyPath, propertyValues);
}
public <C extends Containerable> void addModificationAddContainer(QName propertyQName, C... containerables) throws SchemaException {
addModificationAddContainer(new ItemPath(propertyQName), containerables);
}
public <C extends Containerable> void addModificationAddContainer(ItemPath propertyPath, C... containerables) throws SchemaException {
fillInModificationAddContainer(this, propertyPath, prismContext, containerables);
}
public <C extends Containerable> void addModificationAddContainer(QName propertyQName, PrismContainerValue<C>... containerValues) {
addModificationAddContainer(new ItemPath(propertyQName), containerValues);
}
public <C extends Containerable> void addModificationAddContainer(ItemPath propertyPath, PrismContainerValue<C>... containerValues) {
fillInModificationAddContainer(this, propertyPath, containerValues);
}
public <C extends Containerable> void addModificationDeleteContainer(QName propertyQName, C... containerables) throws SchemaException {
addModificationDeleteContainer(new ItemPath(propertyQName), containerables);
}
public <C extends Containerable> void addModificationDeleteContainer(ItemPath propertyPath, C... containerables) throws SchemaException {
fillInModificationDeleteContainer(this, propertyPath, prismContext, containerables);
}
public <C extends Containerable> void addModificationDeleteContainer(QName propertyQName, PrismContainerValue<C>... containerValues) {
addModificationDeleteContainer(new ItemPath(propertyQName), containerValues);
}
public <C extends Containerable> void addModificationDeleteContainer(ItemPath propertyPath, PrismContainerValue<C>... containerValues) {
fillInModificationDeleteContainer(this, propertyPath, containerValues);
}
public <C extends Containerable> void addModificationReplaceContainer(QName propertyQName, PrismContainerValue<C>... containerValues) {
addModificationReplaceContainer(new ItemPath(propertyQName), containerValues);
}
public <C extends Containerable> void addModificationReplaceContainer(ItemPath propertyPath, PrismContainerValue<C>... containerValues) {
fillInModificationReplaceContainer(this, propertyPath, containerValues);
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationReplaceContainer(Class<O> type, String oid,
ItemPath propertyPath, PrismContext prismContext, C... containerValues) throws SchemaException {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
fillInModificationReplaceContainer(objectDelta, propertyPath, containerValues);
return objectDelta;
}
protected static <O extends Objectable, X> void fillInModificationReplaceProperty(ObjectDelta<O> objectDelta,
ItemPath propertyPath, X... propertyValues) {
PropertyDelta<X> propertyDelta = objectDelta.createPropertyModification(propertyPath);
if (propertyValues != null) {
Collection<PrismPropertyValue<X>> valuesToReplace = toPrismPropertyValues(objectDelta.getPrismContext(), propertyValues);
propertyDelta.setValuesToReplace(valuesToReplace);
objectDelta.addModification(propertyDelta);
}
}
protected static <O extends Objectable, X> void fillInModificationAddProperty(ObjectDelta<O> objectDelta,
ItemPath propertyPath, X... propertyValues) {
PropertyDelta<X> propertyDelta = objectDelta.createPropertyModification(propertyPath);
if (propertyValues != null) {
Collection<PrismPropertyValue<X>> valuesToAdd = toPrismPropertyValues(objectDelta.getPrismContext(), propertyValues);
propertyDelta.addValuesToAdd(valuesToAdd);
objectDelta.addModification(propertyDelta);
}
}
protected static <O extends Objectable, X> void fillInModificationDeleteProperty(ObjectDelta<O> objectDelta,
ItemPath propertyPath, X... propertyValues) {
PropertyDelta<X> propertyDelta = objectDelta.createPropertyModification(propertyPath);
if (propertyValues != null) {
Collection<PrismPropertyValue<X>> valuesToDelete = toPrismPropertyValues(objectDelta.getPrismContext(), propertyValues);
propertyDelta.addValuesToDelete(valuesToDelete);
objectDelta.addModification(propertyDelta);
}
}
public void addModificationAddReference(QName propertyQName, PrismReferenceValue... refValues) {
fillInModificationAddReference(this, new ItemPath(propertyQName), refValues);
}
public void addModificationDeleteReference(QName propertyQName, PrismReferenceValue... refValues) {
fillInModificationDeleteReference(this, new ItemPath(propertyQName), refValues);
}
public void addModificationReplaceReference(QName propertyQName, PrismReferenceValue... refValues) {
fillInModificationReplaceReference(this, new ItemPath(propertyQName), refValues);
}
protected static <O extends Objectable> void fillInModificationReplaceReference(ObjectDelta<O> objectDelta,
ItemPath refPath, PrismReferenceValue... refValues) {
ReferenceDelta refDelta = objectDelta.createReferenceModification(refPath);
if (refValues != null) {
refDelta.setValuesToReplace(refValues);
objectDelta.addModification(refDelta);
}
}
protected static <O extends Objectable> void fillInModificationAddReference(ObjectDelta<O> objectDelta,
ItemPath refPath, PrismReferenceValue... refValues) {
ReferenceDelta refDelta = objectDelta.createReferenceModification(refPath);
if (refValues != null) {
refDelta.addValuesToAdd(refValues);
objectDelta.addModification(refDelta);
}
}
protected static <O extends Objectable> void fillInModificationDeleteReference(ObjectDelta<O> objectDelta,
ItemPath refPath, PrismReferenceValue... refValues) {
ReferenceDelta refDelta = objectDelta.createReferenceModification(refPath);
if (refValues != null) {
refDelta.addValuesToDelete(refValues);
objectDelta.addModification(refDelta);
}
}
private ReferenceDelta createReferenceModification(ItemPath refPath) {
PrismObjectDefinition<T> objDef = getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(getObjectTypeClass());
PrismReferenceDefinition refDef = objDef.findReferenceDefinition(refPath);
return createReferenceModification(refPath, refDef);
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationAddContainer(Class<O> type, String oid,
QName propertyName, PrismContext prismContext, PrismContainerValue<C>... containerValues) {
return createModificationAddContainer(type, oid, new ItemPath(propertyName), prismContext, containerValues);
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationAddContainer(Class<O> type, String oid,
ItemPath propertyPath, PrismContext prismContext, PrismContainerValue<C>... containerValues) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
fillInModificationAddContainer(objectDelta, propertyPath, containerValues);
return objectDelta;
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationAddContainer(Class<O> type, String oid,
QName propertyName, PrismContext prismContext, C... containerValues) throws SchemaException {
return createModificationAddContainer(type, oid, new ItemPath(propertyName), prismContext, containerValues);
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationAddContainer(Class<O> type, String oid,
ItemPath propertyPath, PrismContext prismContext, C... containerValues) throws SchemaException {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
PrismContainerValue<C>[] containerPValues = new PrismContainerValue[containerValues.length];
for (int i=0; i<containerValues.length; i++) {
C containerable = containerValues[i];
prismContext.adopt(containerable, type, propertyPath);
containerPValues[i] = containerable.asPrismContainerValue();
}
fillInModificationAddContainer(objectDelta, propertyPath, containerPValues);
return objectDelta;
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationDeleteContainer(Class<O> type,
String oid, QName containerName, PrismContext prismContext, PrismContainerValue<C>... containerValues) {
return createModificationDeleteContainer(type, oid, new ItemPath(containerName), prismContext, containerValues);
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationDeleteContainer(Class<O> type, String oid, ItemPath containerPath,
PrismContext prismContext, PrismContainerValue<C>... containerValues) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
fillInModificationDeleteContainer(objectDelta, containerPath, containerValues);
return objectDelta;
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationDeleteContainer(Class<O> type, String oid,
QName containerName, PrismContext prismContext, C... containerValues) throws SchemaException {
return createModificationDeleteContainer(type, oid, new ItemPath(containerName), prismContext, containerValues);
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationDeleteContainer(Class<O> type, String oid,
ItemPath propertyPath, PrismContext prismContext, C... containerValues) throws SchemaException {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
PrismContainerValue<C>[] containerPValues = new PrismContainerValue[containerValues.length];
for (int i=0; i<containerValues.length; i++) {
C containerable = containerValues[i];
prismContext.adopt(containerable, type, propertyPath);
containerPValues[i] = containerable.asPrismContainerValue();
}
fillInModificationDeleteContainer(objectDelta, propertyPath, containerPValues);
return objectDelta;
}
protected static <O extends Objectable, C extends Containerable> void fillInModificationDeleteContainer(ObjectDelta<O> objectDelta,
ItemPath propertyPath, PrismContainerValue<C>... containerValues) {
ContainerDelta<C> containerDelta = objectDelta.createContainerModification(propertyPath);
if (containerValues != null && containerValues.length > 0) {
containerDelta.addValuesToDelete(containerValues);
}
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationReplaceContainer(Class<O> type,
String oid, QName containerName, PrismContext prismContext, PrismContainerValue<C>... containerValues) {
return createModificationReplaceContainer(type, oid, new ItemPath(containerName), prismContext, containerValues);
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationReplaceContainer(Class<O> type, String oid, ItemPath containerPath,
PrismContext prismContext, PrismContainerValue<C>... containerValues) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
fillInModificationReplaceContainer(objectDelta, containerPath, containerValues);
return objectDelta;
}
public static <O extends Objectable, C extends Containerable> ObjectDelta<O> createModificationReplaceContainer(Class<O> type, String oid,
QName propertyName, PrismContext prismContext, C... containerValues) throws SchemaException {
return createModificationReplaceContainer(type, oid, new ItemPath(propertyName), prismContext, containerValues);
}
protected static <O extends Objectable, C extends Containerable> void fillInModificationAddContainer(ObjectDelta<O> objectDelta,
ItemPath propertyPath, PrismContainerValue<C>... containerValues) {
ContainerDelta<C> containerDelta = objectDelta.createContainerModification(propertyPath);
if (containerValues != null && containerValues.length > 0) {
containerDelta.addValuesToAdd(containerValues);
}
}
protected static <O extends Objectable, C extends Containerable> void fillInModificationAddContainer(ObjectDelta<O> objectDelta,
ItemPath propertyPath, PrismContext prismContext, C... containerables) throws SchemaException {
ContainerDelta<C> containerDelta = objectDelta.createContainerModification(propertyPath);
if (containerables != null) {
for (C containerable: containerables) {
prismContext.adopt(containerable, objectDelta.getObjectTypeClass(), propertyPath);
PrismContainerValue<C> prismContainerValue = containerable.asPrismContainerValue();
containerDelta.addValueToAdd(prismContainerValue);
}
}
}
protected static <O extends Objectable, C extends Containerable> void fillInModificationDeleteContainer(ObjectDelta<O> objectDelta,
ItemPath propertyPath, PrismContext prismContext, C... containerables) throws SchemaException {
ContainerDelta<C> containerDelta = objectDelta.createContainerModification(propertyPath);
if (containerables != null) {
for (C containerable: containerables) {
prismContext.adopt(containerable, objectDelta.getObjectTypeClass(), propertyPath);
PrismContainerValue<C> prismContainerValue = containerable.asPrismContainerValue();
containerDelta.addValueToDelete(prismContainerValue);
}
}
}
protected static <O extends Objectable, C extends Containerable> void fillInModificationReplaceContainer(ObjectDelta<O> objectDelta,
ItemPath propertyPath, PrismContainerValue<C>... containerValues) {
ContainerDelta<C> containerDelta = objectDelta.createContainerModification(propertyPath);
if (containerValues != null && containerValues.length > 0) {
containerDelta.setValuesToReplace(containerValues);
} else {
// Means: clear all values
containerDelta.setValuesToReplace();
}
}
protected static <O extends Objectable, C extends Containerable> void fillInModificationReplaceContainer(ObjectDelta<O> objectDelta,
ItemPath propertyPath, C... containerValues) throws SchemaException {
if (containerValues != null) {
ContainerDelta<C> containerDelta = objectDelta.createContainerModification(propertyPath);
Collection<PrismContainerValue<C>> valuesToReplace = toPrismContainerValues(objectDelta.getObjectTypeClass(), propertyPath, objectDelta.getPrismContext(), containerValues);
containerDelta.setValuesToReplace(valuesToReplace);
objectDelta.addModification(containerDelta);
}
}
protected static <X> Collection<PrismPropertyValue<X>> toPrismPropertyValues(PrismContext prismContext, X... propertyValues) {
Collection<PrismPropertyValue<X>> pvalues = new ArrayList<PrismPropertyValue<X>>(propertyValues.length);
for (X val: propertyValues) {
PrismUtil.recomputeRealValue(val, prismContext);
PrismPropertyValue<X> pval = new PrismPropertyValue<X>(val);
pvalues.add(pval);
}
return pvalues;
}
protected static <O extends Objectable, C extends Containerable> Collection<PrismContainerValue<C>> toPrismContainerValues(Class<O> type, ItemPath path, PrismContext prismContext, C... containerValues) throws SchemaException {
Collection<PrismContainerValue<C>> pvalues = new ArrayList<PrismContainerValue<C>>(containerValues.length);
for (C val: containerValues) {
prismContext.adopt(val, type, path);
PrismUtil.recomputeRealValue(val, prismContext);
PrismContainerValue<C> pval = val.asPrismContainerValue();
pvalues.add(pval);
}
return pvalues;
}
/**
* Convenience method for quick creation of object deltas that replace a single object property. This is used quite often
* to justify a separate method.
*/
public static <O extends Objectable> ObjectDelta<O> createModificationAddReference(Class<O> type, String oid, QName propertyName,
PrismContext prismContext, PrismObject<?>... referenceObjects) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
PrismObjectDefinition<O> objDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type);
PrismReferenceDefinition refDef = objDef.findReferenceDefinition(propertyName);
ReferenceDelta referenceDelta = objectDelta.createReferenceModification(propertyName, refDef);
Collection<PrismReferenceValue> valuesToReplace = new ArrayList<PrismReferenceValue>(referenceObjects.length);
for (PrismObject<?> refObject: referenceObjects) {
PrismReferenceValue refVal = new PrismReferenceValue();
refVal.setObject(refObject);
valuesToReplace.add(refVal);
}
referenceDelta.setValuesToReplace(valuesToReplace);
return objectDelta;
}
public static <O extends Objectable> ObjectDelta<O> createModificationAddReference(Class<O> type, String oid, QName propertyName,
PrismContext prismContext, String... targetOids) {
PrismReferenceValue[] referenceValues = new PrismReferenceValue[targetOids.length];
for(int i=0; i < targetOids.length; i++) {
referenceValues[i] = new PrismReferenceValue(targetOids[i]);
}
return createModificationAddReference(type, oid, propertyName, prismContext, referenceValues);
}
/**
* Convenience method for quick creation of object deltas that replace a single object property. This is used quite often
* to justify a separate method.
*/
public static <O extends Objectable> ObjectDelta<O> createModificationAddReference(Class<O> type, String oid, QName propertyName,
PrismContext prismContext, PrismReferenceValue... referenceValues) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
PrismObjectDefinition<O> objDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type);
PrismReferenceDefinition refDef = objDef.findReferenceDefinition(propertyName);
ReferenceDelta referenceDelta = objectDelta.createReferenceModification(propertyName, refDef);
Collection<PrismReferenceValue> valuesToAdd = new ArrayList<PrismReferenceValue>(referenceValues.length);
for (PrismReferenceValue refVal: referenceValues) {
valuesToAdd.add(refVal);
}
referenceDelta.addValuesToAdd(valuesToAdd);
return objectDelta;
}
public static <O extends Objectable> ObjectDelta<O> createModificationDeleteReference(Class<O> type, String oid, QName propertyName,
PrismContext prismContext, String... targetOids) {
PrismReferenceValue[] referenceValues = new PrismReferenceValue[targetOids.length];
for(int i=0; i < targetOids.length; i++) {
referenceValues[i] = new PrismReferenceValue(targetOids[i]);
}
return createModificationDeleteReference(type, oid, propertyName, prismContext, referenceValues);
}
/**
* Convenience method for quick creation of object deltas that replace a single object property. This is used quite often
* to justify a separate method.
*/
public static <O extends Objectable> ObjectDelta<O> createModificationDeleteReference(Class<O> type, String oid, QName propertyName,
PrismContext prismContext, PrismReferenceValue... referenceValues) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
objectDelta.setOid(oid);
PrismObjectDefinition<O> objDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type);
PrismReferenceDefinition refDef = objDef.findReferenceDefinition(propertyName);
ReferenceDelta referenceDelta = objectDelta.createReferenceModification(propertyName, refDef);
Collection<PrismReferenceValue> valuesToDelete = new ArrayList<PrismReferenceValue>(referenceValues.length);
for (PrismReferenceValue refVal: referenceValues) {
valuesToDelete.add(refVal);
}
referenceDelta.addValuesToDelete(valuesToDelete);
return objectDelta;
}
public static <T extends Objectable> ObjectDelta<T> createModifyDelta(String oid, ItemDelta modification,
Class<T> objectTypeClass, PrismContext prismContext) {
Collection modifications = new ArrayList<ItemDelta>(1);
modifications.add(modification);
return createModifyDelta(oid, modifications, objectTypeClass, prismContext);
}
public static <T extends Objectable> ObjectDelta<T> createModifyDelta(String oid, Collection<? extends ItemDelta> modifications,
Class<T> objectTypeClass, PrismContext prismContext) {
ObjectDelta<T> objectDelta = new ObjectDelta<T>(objectTypeClass, ChangeType.MODIFY, prismContext);
objectDelta.addModifications(modifications);
objectDelta.setOid(oid);
return objectDelta;
}
public static <O extends Objectable> ObjectDelta<O> createEmptyAddDelta(Class<O> type, String oid, PrismContext prismContext) throws SchemaException {
ObjectDelta<O> objectDelta = createEmptyDelta(type, oid, prismContext, ChangeType.ADD);
PrismObjectDefinition<O> objDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type);
PrismObject<O> objectToAdd = objDef.instantiate();
objectDelta.setObjectToAdd(objectToAdd);
return objectDelta;
}
public static <O extends Objectable> ObjectDelta<O> createEmptyModifyDelta(Class<O> type, String oid, PrismContext prismContext) {
return createEmptyDelta(type, oid, prismContext, ChangeType.MODIFY);
}
public static <O extends Objectable> ObjectDelta<O> createEmptyDeleteDelta(Class<O> type, String oid, PrismContext prismContext) {
return createEmptyDelta(type, oid, prismContext, ChangeType.DELETE);
}
public static <O extends Objectable> ObjectDelta<O> createEmptyDelta(Class<O> type, String oid, PrismContext prismContext,
ChangeType changeType) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, changeType, prismContext);
objectDelta.setOid(oid);
return objectDelta;
}
public static <O extends Objectable> ObjectDelta<O> createAddDelta(PrismObject<O> objectToAdd) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(objectToAdd.getCompileTimeClass(), ChangeType.ADD, objectToAdd.getPrismContext());
objectDelta.setOid(objectToAdd.getOid());
objectDelta.setObjectToAdd(objectToAdd);
return objectDelta;
}
public static <O extends Objectable> ObjectDelta<O> createDeleteDelta(Class<O> type, String oid, PrismContext prismContext) {
ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.DELETE, prismContext);
objectDelta.setOid(oid);
return objectDelta;
}
public ObjectDelta<T> createReverseDelta() throws SchemaException {
if (isAdd()) {
return createDeleteDelta(getObjectTypeClass(), getOid(), getPrismContext());
}
if (isDelete()) {
throw new SchemaException("Cannot reverse delete delta");
}
ObjectDelta<T> reverseDelta = createEmptyModifyDelta(getObjectTypeClass(), getOid(), getPrismContext());
for (ItemDelta<?,?> modification: getModifications()) {
reverseDelta.addModification(modification.createReverseDelta());
}
return reverseDelta;
}
public void checkConsistence() {
checkConsistence(ConsistencyCheckScope.THOROUGH);
}
public void checkConsistence(ConsistencyCheckScope scope) {
checkConsistence(true, false, false, scope);
}
public void checkConsistence(boolean requireOid, boolean requireDefinition, boolean prohibitRaw) {
checkConsistence(requireOid, requireDefinition, prohibitRaw, ConsistencyCheckScope.THOROUGH);
}
public void checkConsistence(boolean requireOid, boolean requireDefinition, boolean prohibitRaw, ConsistencyCheckScope scope) {
if (scope.isThorough() && prismContext == null) {
throw new IllegalStateException("No prism context in "+this);
}
if (getChangeType() == ChangeType.ADD) {
if (scope.isThorough() && getModifications() != null && !getModifications().isEmpty()) {
throw new IllegalStateException("Modifications present in ADD delta "+this);
}
if (getObjectToAdd() != null) {
getObjectToAdd().checkConsistence(requireDefinition, prohibitRaw, scope);
} else {
throw new IllegalStateException("User primary delta is ADD, but there is not object to add in "+this);
}
} else if (getChangeType() == ChangeType.MODIFY) {
if (scope.isThorough()) {
checkIdentifierConsistence(requireOid);
if (getObjectToAdd() != null) {
throw new IllegalStateException("Object to add present in MODIFY delta "+this);
}
if (getModifications() == null) {
throw new IllegalStateException("Null modification in MODIFY delta "+this);
}
}
ItemDelta.checkConsistence(getModifications(), requireDefinition, prohibitRaw, scope);
} else if (getChangeType() == ChangeType.DELETE) {
if (scope.isThorough()) {
if (requireOid && getOid() == null) {
throw new IllegalStateException("Null oid in delta "+this);
}
if (getObjectToAdd() != null) {
throw new IllegalStateException("Object to add present in DELETE delta "+this);
}
if (getModifications() != null && !getModifications().isEmpty()) {
throw new IllegalStateException("Modifications present in DELETE delta "+this);
}
}
} else {
throw new IllegalStateException("Unknown change type "+getChangeType()+" in delta "+this);
}
}
protected void checkIdentifierConsistence(boolean requireOid) {
if (requireOid && getOid() == null) {
throw new IllegalStateException("Null oid in delta "+this);
}
}
public static void checkConsistence(Collection<? extends ObjectDelta<?>> deltas) {
for (ObjectDelta<?> delta: deltas) {
delta.checkConsistence();
}
}
public void assertDefinitions() throws SchemaException {
assertDefinitions("");
}
public void assertDefinitions(String sourceDescription) throws SchemaException {
assertDefinitions(false, sourceDescription);
}
public void assertDefinitions(boolean tolerateRawElements) throws SchemaException {
assertDefinitions(tolerateRawElements, "");
}
/**
* Assert that all the items has appropriate definition.
*/
public void assertDefinitions(boolean tolerateRawElements, String sourceDescription) throws SchemaException {
if (changeType == ChangeType.ADD) {
objectToAdd.assertDefinitions("add delta in "+sourceDescription);
}
if (changeType == ChangeType.MODIFY) {
for (ItemDelta<?,?> mod: modifications) {
mod.assertDefinitions(tolerateRawElements, "modify delta for "+getOid()+" in "+sourceDescription);
}
}
}
public void revive(PrismContext prismContext) throws SchemaException {
if (objectToAdd != null) {
objectToAdd.revive(prismContext);
}
if (modifications != null) {
for (ItemDelta<?,?> modification: modifications) {
modification.revive(prismContext);
}
}
// todo is this correct? [pm]
if (this.prismContext == null) {
this.prismContext = prismContext;
}
}
public void applyDefinition(PrismObjectDefinition<T> objectDefinition, boolean force) throws SchemaException {
if (objectToAdd != null) {
objectToAdd.applyDefinition(objectDefinition, force);
}
if (modifications != null) {
for (ItemDelta modification: modifications) {
ItemPath path = modification.getPath();
ItemDefinition itemDefinition = objectDefinition.findItemDefinition(path);
modification.applyDefinition(itemDefinition, force);
}
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((changeType == null) ? 0 : changeType.hashCode());
result = prime * result
+ ((objectToAdd == null) ? 0 : objectToAdd.hashCode());
result = prime * result
+ ((objectTypeClass == null) ? 0 : objectTypeClass.hashCode());
result = prime * result + ((oid == null) ? 0 : oid.hashCode());
return result;
}
public boolean equivalent(ObjectDelta other) {
if (changeType != other.changeType)
return false;
if (objectToAdd == null) {
if (other.objectToAdd != null)
return false;
} else if (!objectToAdd.equivalent(other.objectToAdd))
return false;
if (!MiscUtil.unorderedCollectionEquals(this.modifications, other.modifications,
(o1, o2) -> o1.equivalent((ItemDelta)o2))) {
return false;
}
return Objects.equals(objectTypeClass, other.objectTypeClass)
&& Objects.equals(oid, other.oid);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ObjectDelta<?> other = (ObjectDelta<?>) obj;
if (changeType != other.changeType)
return false;
if (modifications == null) {
if (other.modifications != null)
return false;
} else if (!MiscUtil.unorderedCollectionEquals(this.modifications,other.modifications))
return false;
if (objectToAdd == null) {
if (other.objectToAdd != null)
return false;
} else if (!objectToAdd.equals(other.objectToAdd))
return false;
if (objectTypeClass == null) {
if (other.objectTypeClass != null)
return false;
} else if (!objectTypeClass.equals(other.objectTypeClass))
return false;
if (oid == null) {
if (other.oid != null)
return false;
} else if (!oid.equals(other.oid))
return false;
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(debugName());
sb.append("(").append(debugIdentifiers());
sb.append(",").append(changeType).append(": ");
if (changeType == ChangeType.ADD) {
if (objectToAdd == null) {
sb.append("null");
} else {
sb.append(objectToAdd.toString());
}
} else if (changeType == ChangeType.MODIFY) {
Iterator<? extends ItemDelta> i = modifications.iterator();
while (i.hasNext()) {
sb.append(i.next().toString());
if (i.hasNext()) {
sb.append(", ");
}
}
}
// Nothing to print for delete
sb.append(")");
return sb.toString();
}
protected String debugName() {
return "ObjectDelta";
}
protected String debugIdentifiers() {
return toDebugType()+":" + getOid();
}
/**
* Returns short string identification of object type. It should be in a form
* suitable for log messages. There is no requirement for the type name to be unique,
* but it rather has to be compact. E.g. short element names are preferred to long
* QNames or URIs.
* @return
*/
public String toDebugType() {
if (objectTypeClass == null) {
return "(unknown)";
}
return objectTypeClass.getSimpleName();
}
@Override
public String debugDump() {
return debugDump(0);
}
@Override
public String debugDump(int indent) {
StringBuilder sb = new StringBuilder();
DebugUtil.indentDebugDump(sb, indent);
sb.append(debugName());
sb.append("<").append(objectTypeClass.getSimpleName()).append(">(");
sb.append(debugIdentifiers()).append(",").append(changeType);
if (changeType == ChangeType.DELETE) {
// Nothing to print for delete
sb.append(")");
} else {
sb.append("):\n");
if (objectToAdd == null) {
if (changeType == ChangeType.ADD) {
DebugUtil.indentDebugDump(sb, indent + 1);
sb.append("null");
}
} else {
sb.append(objectToAdd.debugDump(indent + 1));
}
if (modifications != null) {
Iterator<? extends ItemDelta> i = modifications.iterator();
while (i.hasNext()) {
sb.append(i.next().debugDump(indent + 1));
if (i.hasNext()) {
sb.append("\n");
}
}
}
}
return sb.toString();
}
public static boolean isNullOrEmpty(ObjectDelta delta) {
return delta == null || delta.isEmpty();
}
/**
* Returns modifications that are related to the given paths; removes them from the original delta.
* Applicable only to modify deltas.
* Currently compares paths by "equals" predicate -- in the future we might want to treat sub/super/equivalent paths!
* So consider this method highly experimental.
*
* @param paths
* @return
*/
public ObjectDelta<T> subtract(@NotNull Collection<ItemPath> paths) {
if (!isModify()) {
throw new UnsupportedOperationException("Only for MODIFY deltas, not for " + this);
}
ObjectDelta<T> rv = new ObjectDelta<T>(this.objectTypeClass, ChangeType.MODIFY, this.prismContext);
rv.oid = this.oid;
Iterator<? extends ItemDelta<?, ?>> iterator = modifications.iterator();
while (iterator.hasNext()) {
ItemDelta<?, ?> itemDelta = iterator.next();
if (paths.contains(itemDelta.getPath())) {
rv.addModification(itemDelta);
iterator.remove();
}
}
return rv;
}
/**
* Checks if the delta tries to add (or set) a 'value' for the item identified by 'itemPath'. If yes, it removes it.
*
* TODO consider changing return value to 'incremental delta' (or null)
*
* @param itemPath
* @param value
* @param dryRun only testing if value could be subtracted; not changing anything
* @return true if the delta originally contained an instruction to add (or set) 'itemPath' to 'value'.
*/
public boolean subtract(@NotNull ItemPath itemPath, @NotNull PrismValue value, boolean fromMinusSet, boolean dryRun) {
if (isAdd()) {
return !fromMinusSet && subtractFromObject(objectToAdd, itemPath, value, dryRun);
} else {
return subtractFromModifications(modifications, itemPath, value, fromMinusSet, dryRun);
}
}
public static boolean subtractFromModifications(Collection<? extends ItemDelta<?, ?>> modifications,
@NotNull ItemPath itemPath, @NotNull PrismValue value, boolean fromMinusSet, boolean dryRun) {
if (modifications == null) {
return false;
}
boolean wasPresent = false;
Iterator<? extends ItemDelta<?, ?>> itemDeltaIterator = modifications.iterator();
while (itemDeltaIterator.hasNext()) {
ItemDelta<?, ?> itemDelta = itemDeltaIterator.next();
if (itemPath.equivalent(itemDelta.getPath())) {
if (!fromMinusSet) {
if (dryRun) {
wasPresent = wasPresent
|| CollectionUtils.emptyIfNull(itemDelta.getValuesToAdd()).contains(value)
|| CollectionUtils.emptyIfNull(itemDelta.getValuesToReplace()).contains(value);
} else {
boolean removed1 = itemDelta.removeValueToAdd(value);
boolean removed2 = itemDelta.removeValueToReplace(value);
wasPresent = wasPresent || removed1 || removed2;
}
} else {
if (itemDelta.getValuesToReplace() != null) {
throw new UnsupportedOperationException("Couldn't subtract 'value to be deleted' from REPLACE itemDelta: " + itemDelta);
}
if (dryRun) {
wasPresent = wasPresent || CollectionUtils.emptyIfNull(itemDelta.getValuesToDelete()).contains(value);
} else {
wasPresent = wasPresent || itemDelta.removeValueToDelete(value);
}
}
if (!dryRun && itemDelta.isInFactEmpty()) {
itemDeltaIterator.remove();
}
}
}
return wasPresent;
}
public static boolean subtractFromObject(@NotNull PrismObject<?> object, @NotNull ItemPath itemPath,
@NotNull PrismValue value, boolean dryRun) {
Item<PrismValue, ItemDefinition> item = object.findItem(itemPath);
if (item == null) {
return false;
}
if (dryRun) {
return item.contains(value);
} else {
return item.remove(value);
}
}
@NotNull
public List<ItemPath> getModifiedItems() {
if (!isModify()) {
throw new UnsupportedOperationException("Supported only for modify deltas");
}
return modifications.stream().map(ItemDelta::getPath).collect(Collectors.toList());
}
public List<PrismValue> getNewValuesFor(ItemPath itemPath) {
if (isAdd()) {
Item<PrismValue, ItemDefinition> item = objectToAdd.findItem(itemPath);
return item != null ? item.getValues() : Collections.emptyList();
} else if (isDelete()) {
return Collections.emptyList();
} else {
ItemDelta itemDelta = ItemDelta.findItemDelta(modifications, itemPath, ItemDelta.class);
if (itemDelta != null) {
if (itemDelta.getValuesToReplace() != null) {
return (List<PrismValue>) itemDelta.getValuesToReplace();
} else if (itemDelta.getValuesToAdd() != null) {
return (List<PrismValue>) itemDelta.getValuesToAdd();
} else {
return Collections.emptyList();
}
} else {
return Collections.emptyList();
}
}
}
/**
* Limitations:
* (1) For DELETE object delta, we don't know what values were in the object's item.
* (2) For REPLACE item delta, we don't know what values were in the object's item (but these deltas are quite rare
* for multivalued items; and eventually there will be normalized into ADD+DELETE form)
* (3) For DELETE item delta for PrismContainers, content of items deleted might not be known
* (only ID could be provided on PCVs).
*/
@SuppressWarnings("unused") // used from scripts
public List<PrismValue> getDeletedValuesFor(ItemPath itemPath) {
if (isAdd()) {
return Collections.emptyList();
} else if (isDelete()) {
return Collections.emptyList();
} else {
ItemDelta itemDelta = ItemDelta.findItemDelta(modifications, itemPath, ItemDelta.class);
if (itemDelta != null) {
if (itemDelta.getValuesToDelete() != null) {
return (List<PrismValue>) itemDelta.getValuesToDelete();
} else {
return Collections.emptyList();
}
} else {
return Collections.emptyList();
}
}
}
}