/*
* 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 java.util.*;
import javax.xml.namespace.QName;
import com.evolveum.midpoint.prism.*;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
public class ContainerDelta<V extends Containerable> extends ItemDelta<PrismContainerValue<V>,PrismContainerDefinition<V>> implements PrismContainerable<V> {
public ContainerDelta(PrismContainerDefinition itemDefinition, PrismContext prismContext) {
super(itemDefinition, prismContext);
}
public ContainerDelta(ItemPath propertyPath, PrismContainerDefinition itemDefinition, PrismContext prismContext) {
super(propertyPath, itemDefinition, prismContext);
}
public ContainerDelta(ItemPath parentPath, QName name, PrismContainerDefinition itemDefinition, PrismContext prismContext) {
super(parentPath, name, itemDefinition, prismContext);
// Extra check. It makes no sense to create container delta with object definition
if (itemDefinition instanceof PrismObjectDefinition<?>) {
throw new IllegalArgumentException("Cannot apply "+definition+" to container delta");
}
}
public ContainerDelta(QName name, PrismContainerDefinition itemDefinition, PrismContext prismContext) {
super(name, itemDefinition, prismContext);
// Extra check. It makes no sense to create container delta with object definition
if (itemDefinition instanceof PrismObjectDefinition<?>) {
throw new IllegalArgumentException("Cannot apply "+definition+" to container delta");
}
}
@Override
public Class<PrismContainer> getItemClass() {
return PrismContainer.class;
}
/**
* Returns all values regardless of whether they are added or removed or replaced.
* Useful for iterating over all the changed values.
*/
public <T extends Containerable> Collection<PrismContainerValue<T>> getValues(Class<T> type) {
checkConsistence();
if (valuesToReplace != null) {
return (Collection) valuesToReplace;
}
return (Collection) MiscUtil.union(valuesToAdd, valuesToDelete);
}
@Override
public PrismContainerDefinition<V> getDefinition() {
return (PrismContainerDefinition<V>) super.getDefinition();
}
@Override
public void setDefinition(PrismContainerDefinition<V> definition) {
if (!(definition instanceof PrismContainerDefinition)) {
throw new IllegalArgumentException("Cannot apply "+definition+" to container delta");
}
// Extra check. It makes no sense to create container delta with object definition
if (definition instanceof PrismObjectDefinition<?>) {
throw new IllegalArgumentException("Cannot apply "+definition+" to container delta");
}
super.setDefinition(definition);
}
@Override
public void applyDefinition(PrismContainerDefinition<V> definition) throws SchemaException {
if (!(definition instanceof PrismContainerDefinition)) {
throw new IllegalArgumentException("Cannot apply definition "+definition+" to container delta "+this);
}
super.applyDefinition(definition);
}
@Override
public boolean hasCompleteDefinition() {
if (!super.hasCompleteDefinition()) {
return false;
}
if (!hasCompleteDefinition(getValuesToAdd())) {
return false;
}
if (!hasCompleteDefinition(getValuesToDelete())) {
return false;
}
if (!hasCompleteDefinition(getValuesToReplace())) {
return false;
}
return true;
}
private boolean hasCompleteDefinition(Collection<PrismContainerValue<V>> values) {
if (values == null) {
return true;
}
for (PrismContainerValue<V> value: values) {
if (!value.hasCompleteDefinition()) {
return false;
}
}
return true;
}
@Override
public Class<V> getCompileTimeClass() {
if (getDefinition() != null) {
return getDefinition().getCompileTimeClass();
}
return null;
}
@Override
protected boolean isApplicableToType(Item item) {
return item instanceof PrismContainer;
}
@Override
public ItemDelta<?,?> getSubDelta(ItemPath path) {
if (path.isEmpty()) {
return this;
}
Long id = null;
ItemPathSegment first = path.first();
if (first instanceof IdItemPathSegment) {
id = ((IdItemPathSegment)first).getId();
path = path.rest();
}
ItemDefinition itemDefinition = getDefinition().findItemDefinition(path);
if (itemDefinition == null) {
throw new IllegalStateException("No definition of " + path + " in " + getDefinition());
}
ItemDelta<?,?> itemDelta = itemDefinition.createEmptyDelta(getPath().subPath(path));
itemDelta.addValuesToAdd(findItemValues(id, path, getValuesToAdd()));
itemDelta.addValuesToDelete(findItemValues(id, path, getValuesToDelete()));
itemDelta.setValuesToReplace(findItemValues(id, path, getValuesToReplace()));
if (itemDelta.isEmpty()) {
return null;
}
return itemDelta;
}
private Collection findItemValues(Long id, ItemPath path, Collection<PrismContainerValue<V>> cvalues) {
if (cvalues == null) {
return null;
}
Collection<PrismValue> subValues = new ArrayList<PrismValue>();
for (PrismContainerValue<V> cvalue: cvalues) {
if (id == null || id == cvalue.getId()) {
Item<?,?> item = cvalue.findItem(path);
if (item != null) {
subValues.addAll(PrismValue.cloneCollection(item.getValues()));
}
}
}
return subValues;
}
/**
* Post processing of delta to expand missing values from the object. E.g. a delete deltas may
* be "id-only" so they contain only id of the value to delete. In such case locate the full value
* in the object and fill it into the delta.
* This method may even delete in-only values that are no longer present in the object.
*/
public <O extends Objectable> void expand(PrismObject<O> object) throws SchemaException {
if (valuesToDelete != null) {
ItemPath path = this.getPath();
PrismContainer<Containerable> container = null;
if (object != null) {
container = object.findContainer(path);
}
Iterator<PrismContainerValue<V>> iterator = valuesToDelete.iterator();
while (iterator.hasNext()) {
PrismContainerValue<V> deltaCVal = iterator.next();
if ((deltaCVal.getItems() == null || deltaCVal.getItems().isEmpty())) {
Long id = deltaCVal.getId();
if (id == null) {
throw new IllegalArgumentException("No id and no items in value "+deltaCVal+" in delete set in "+this);
}
if (container != null) {
PrismContainerValue<Containerable> containerCVal = container.findValue(id);
if (containerCVal != null) {
for (Item<?,?> containerItem: containerCVal.getItems()) {
deltaCVal.add(containerItem.clone());
}
continue;
}
}
// id-only value with ID that is not in the object any more: delete the value from delta
iterator.remove();
}
}
}
}
@Override
protected boolean isValueEquivalent(PrismContainerValue<V> a, PrismContainerValue<V> b) {
if (!super.isValueEquivalent(a, b)) {
return false;
}
if (a.getId() == null || b.getId() == null) {
return true;
} else {
return a.getId().equals(b.getId());
}
}
@Override
public void checkConsistence(boolean requireDefinition, boolean prohibitRaw, ConsistencyCheckScope scope) {
super.checkConsistence(requireDefinition, prohibitRaw, scope);
checkDuplicateId(valuesToAdd);
}
private void checkDuplicateId(Collection<PrismContainerValue<V>> valuesToAdd) {
if (valuesToAdd == null || valuesToAdd.isEmpty()) {
return;
}
Set<Long> idsToAdd = new HashSet<>();
for (PrismContainerValue<V> valueToAdd : valuesToAdd) {
Long id = valueToAdd.getId();
if (id != null) {
if (idsToAdd.contains(id)) {
throw new IllegalArgumentException("Trying to add prism container value with id " + id + " multiple times");
} else {
idsToAdd.add(id);
}
}
}
}
@Override
public ContainerDelta<V> clone() {
ContainerDelta<V> clone = new ContainerDelta<V>(getElementName(), getDefinition(), getPrismContext());
copyValues(clone);
return clone;
}
protected void copyValues(ContainerDelta<V> clone) {
super.copyValues(clone);
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createDelta(QName containerName,
Class<O> type, PrismContext prismContext) {
return createDelta(new ItemPath(containerName), type, prismContext);
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createDelta(ItemPath containerPath,
Class<O> type, PrismContext prismContext) {
PrismObjectDefinition<O> objectDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type);
return createDelta(containerPath, objectDefinition);
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createDelta(QName containerName,
PrismObjectDefinition<O> objectDefinition) {
return createDelta(new ItemPath(containerName), objectDefinition);
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createDelta(ItemPath containerPath,
PrismObjectDefinition<O> objectDefinition) {
PrismContainerDefinition<T> containerDefinition = objectDefinition.findContainerDefinition(containerPath);
if (containerDefinition == null) {
throw new IllegalArgumentException("No definition for "+containerPath+" in "+objectDefinition);
}
ContainerDelta<T> delta = new ContainerDelta<T>(containerPath, containerDefinition, objectDefinition.getPrismContext());
return delta;
}
public static <T extends Containerable> ContainerDelta<T> createDelta(QName containerName, PrismContainerDefinition<T> containerDefinition) {
ContainerDelta<T> delta = new ContainerDelta<T>(new ItemPath(containerName), containerDefinition, containerDefinition.getPrismContext());
return delta;
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationAdd(QName containerName,
Class<O> type, PrismContext prismContext, T containerable) throws SchemaException {
return createModificationAdd(new ItemPath(containerName), type, prismContext, containerable);
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationAdd(ItemPath containerPath,
Class<O> type, PrismContext prismContext, T containerable) throws SchemaException {
return createModificationAdd(containerPath, type, prismContext, containerable.asPrismContainerValue());
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationAdd(QName containerName,
Class<O> type, PrismContext prismContext, PrismContainerValue<T> cValue) throws SchemaException {
return createModificationAdd(new ItemPath(containerName), type, prismContext, cValue);
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationAdd(ItemPath containerPath,
Class<O> type, PrismContext prismContext, PrismContainerValue<T> cValue) throws SchemaException {
ContainerDelta<T> delta = createDelta(containerPath, type, prismContext);
prismContext.adopt(cValue, type, containerPath);
delta.addValuesToAdd(cValue);
return delta;
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationDelete(QName containerName,
Class<O> type, PrismContext prismContext, T containerable) throws SchemaException {
return createModificationDelete(new ItemPath(containerName), type, prismContext, containerable);
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationDelete(ItemPath containerPath,
Class<O> type, PrismContext prismContext, T containerable) throws SchemaException {
return createModificationDelete(containerPath, type, prismContext, containerable.asPrismContainerValue());
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationDelete(QName containerName,
Class<O> type, PrismContext prismContext, PrismContainerValue<T> cValue) throws SchemaException {
return createModificationDelete(new ItemPath(containerName), type, prismContext, cValue);
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationDelete(ItemPath containerPath,
Class<O> type, PrismContext prismContext, PrismContainerValue<T> cValue) throws SchemaException {
ContainerDelta<T> delta = createDelta(containerPath, type, prismContext);
prismContext.adopt(cValue, type, containerPath);
delta.addValuesToDelete(cValue);
return delta;
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationReplace(QName containerName,
Class<O> type, PrismContext prismContext, T containerable) throws SchemaException {
return createModificationReplace(new ItemPath(containerName), type, prismContext, containerable);
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationReplace(QName containerName,
Class<O> type, PrismContext prismContext, Collection<T> containerables) throws SchemaException {
return createModificationReplace(new ItemPath(containerName), type, prismContext, containerables);
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationReplace(ItemPath containerPath,
Class<O> type, PrismContext prismContext, T containerable) throws SchemaException {
return createModificationReplace(containerPath, type, prismContext, containerable.asPrismContainerValue());
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationReplace(ItemPath containerPath,
Class<O> type, PrismContext prismContext, Collection<T> containerables) throws SchemaException {
ContainerDelta<T> delta = createDelta(containerPath, type, prismContext);
List<PrismContainerValue<T>> pcvs = new ArrayList<>();
for (Containerable c: containerables) {
pcvs.add(c.asPrismContainerValue());
}
delta.setValuesToReplace(pcvs);
return delta;
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationReplace(QName containerName,
Class<O> type, PrismContext prismContext, PrismContainerValue<T> cValue) throws SchemaException {
return createModificationReplace(new ItemPath(containerName), type, prismContext, cValue);
}
public static <T extends Containerable,O extends Objectable> ContainerDelta<T> createModificationReplace(ItemPath containerPath,
Class<O> type, PrismContext prismContext, PrismContainerValue<T> cValue) throws SchemaException {
ContainerDelta<T> delta = createDelta(containerPath, type, prismContext);
prismContext.adopt(cValue, type, containerPath);
delta.setValuesToReplace(cValue);
return delta;
}
// cValue should be parent-less
public static <T extends Containerable> ContainerDelta<T> createModificationReplace(QName containerName, PrismContainerDefinition containerDefinition, PrismContainerValue<T> cValue) throws SchemaException {
ContainerDelta<T> delta = createDelta(containerName, containerDefinition);
delta.setValuesToReplace(cValue);
return delta;
}
// cValues should be parent-less
public static Collection<? extends ItemDelta> createModificationReplaceContainerCollection(QName containerName,
PrismObjectDefinition<?> objectDefinition, PrismContainerValue... cValues) {
Collection<? extends ItemDelta> modifications = new ArrayList<ItemDelta>(1);
ContainerDelta delta = createDelta(containerName, objectDefinition.findContainerDefinition(containerName));
delta.setValuesToReplace(cValues);
((Collection)modifications).add(delta);
return modifications;
}
// cValues should be parent-less
public static <T extends Containerable> ContainerDelta<T> createModificationReplace(QName containerName, PrismObjectDefinition<?> objectDefinition, PrismContainerValue... cValues) {
ContainerDelta delta = createDelta(containerName, objectDefinition.findContainerDefinition(containerName));
delta.setValuesToReplace(cValues);
return delta;
}
@Override
protected void dumpValues(StringBuilder sb, String label, Collection<PrismContainerValue<V>> values, int indent) {
DebugUtil.debugDumpLabel(sb, label, indent);
if (values == null) {
sb.append(" (null)");
} else if (values.isEmpty()) {
sb.append(" (empty)");
} else {
for (PrismContainerValue<V> val: values) {
sb.append("\n");
sb.append(val.debugDump(indent+1));
}
}
}
}