/* * 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; import java.lang.reflect.Modifier; import java.util.*; import java.util.stream.Collectors; import javax.xml.namespace.QName; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.marshaller.JaxbDomHack; import com.evolveum.midpoint.prism.path.*; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.PrettyPrinter; import com.evolveum.midpoint.util.QNameUtil; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.midpoint.util.exception.SystemException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * @author semancik * */ public class PrismContainerValue<C extends Containerable> extends PrismValue implements DebugDumpable { private static final Trace LOGGER = TraceManager.getTrace(PrismContainerValue.class); // This is list. We need to maintain the order internally to provide consistent // output in DOM and other ordering-sensitive representations protected List<Item<?,?>> items = null; private Long id; private C containerable = null; // Definition of this value. Usually it is the same as CTD declared in the parent container. // However, in order to support polymorphism (as well as parent-less values) we distinguish between PC and PCV type definition. protected ComplexTypeDefinition complexTypeDefinition = null; public PrismContainerValue() { } public PrismContainerValue(C containerable) { this(containerable, null); } public PrismContainerValue(PrismContext prismContext) { this(null, prismContext); } public PrismContainerValue(C containerable, PrismContext prismContext) { super(prismContext); this.containerable = containerable; if (prismContext != null) { getComplexTypeDefinition(); // to determine CTD (could be also called with null prismContext, but non-null prismContext provides additional information in some cases) } } public PrismContainerValue(OriginType type, Objectable source, PrismContainerable container, Long id, ComplexTypeDefinition complexTypeDefinition, PrismContext prismContext) { super(prismContext, type, source, container); this.id = id; this.complexTypeDefinition = complexTypeDefinition; } @Override public PrismContext getPrismContext() { if (prismContext != null) { return prismContext; } if (complexTypeDefinition != null && complexTypeDefinition.getPrismContext() != null) { return complexTypeDefinition.getPrismContext(); } if (getParent() != null) { return getParent().getPrismContext(); } return null; } // Primarily for testing public PrismContext getPrismContextLocal() { return prismContext; } /** * Returns a set of items that the property container contains. The items may be properties or inner property containers. * <p/> * The set may be null. In case there are no properties an empty set is * returned. * <p/> * Returned set is mutable. Live object is returned. * * @return set of items that the property container contains. */ public List<Item<?,?>> getItems() { if (items == null) { return null; } if (isImmutable()) { return Collections.unmodifiableList(items); } else { return items; } } public Item<?,?> getNextItem(Item<?,?> referenceItem) { if (items == null) { return null; } Iterator<Item<?,?>> iterator = items.iterator(); while (iterator.hasNext()) { Item<?,?> item = iterator.next(); if (item == referenceItem) { if (iterator.hasNext()) { return iterator.next(); } else { return null; } } } throw new IllegalArgumentException("Item "+referenceItem+" is not part of "+this); } public Item<?,?> getPreviousItem(Item<?,?> referenceItem) { if (items == null){ return null; } Item<?,?> lastItem = null; Iterator<Item<?,?>> iterator = items.iterator(); //noinspection WhileLoopReplaceableByForEach while (iterator.hasNext()) { Item<?,?> item = iterator.next(); if (item == referenceItem) { return lastItem; } lastItem = item; } throw new IllegalArgumentException("Item "+referenceItem+" is not part of "+this); } /** * Returns a set of properties that the property container contains. * <p/> * The set must not be null. In case there are no properties an empty set is * returned. * <p/> * Returned set is immutable! Any change to it will be ignored. * * @return set of properties that the property container contains. */ public Set<PrismProperty<?>> getProperties() { Set<PrismProperty<?>> properties = new HashSet<PrismProperty<?>>(); if (items != null) { for (Item<?,?> item : getItems()) { if (item instanceof PrismProperty) { properties.add((PrismProperty<?>) item); } } } return properties; } public Long getId() { return id; } public void setId(Long id) { checkMutability(); this.id = id; } @SuppressWarnings("unchecked") public PrismContainerable<C> getParent() { Itemable parent = super.getParent(); if (parent == null) { return null; } if (!(parent instanceof PrismContainerable)) { throw new IllegalStateException("Expected that parent of "+PrismContainerValue.class.getName()+" will be "+ PrismContainerable.class.getName()+", but it is "+parent.getClass().getName()); } return (PrismContainerable<C>)parent; } @SuppressWarnings("unchecked") public PrismContainer<C> getContainer() { Itemable parent = super.getParent(); if (parent == null) { return null; } if (!(parent instanceof PrismContainer)) { throw new IllegalStateException("Expected that parent of "+PrismContainerValue.class.getName()+" will be "+ PrismContainer.class.getName()+", but it is "+parent.getClass().getName()); } return (PrismContainer<C>)super.getParent(); } @NotNull public ItemPath getPath() { Itemable parent = getParent(); ItemPath parentPath = ItemPath.EMPTY_PATH; if (parent != null) { parentPath = parent.getPath(); } if (getId() != null) { return ItemPath.subPath(parentPath, new IdItemPathSegment(getId())); } else { return parentPath; } } // For compatibility with other PrismValue types public C getValue() { return asContainerable(); } @SuppressWarnings("unchecked") public C asContainerable() { if (containerable != null) { return containerable; } if (getParent() == null && complexTypeDefinition == null) { throw new IllegalStateException("Cannot represent container value without a parent and complex type definition as containerable; value: " + this); } return asContainerableInternal(resolveClass(null)); } // returned class must be of type 'requiredClass' (or any of its subtypes) public C asContainerable(Class<C> requiredClass) { if (containerable != null) { return containerable; } return asContainerableInternal(resolveClass(requiredClass)); } private Class<C> resolveClass(@Nullable Class<C> requiredClass) { if (complexTypeDefinition != null && complexTypeDefinition.getCompileTimeClass() != null) { Class<?> actualClass = complexTypeDefinition.getCompileTimeClass(); if (requiredClass != null && !requiredClass.isAssignableFrom(actualClass)) { throw new IllegalStateException("asContainerable was called to produce " + requiredClass + ", but the actual class in PCV is " + actualClass); } else { return (Class<C>) actualClass; } } else { PrismContainerable parent = getParent(); if (parent != null) { Class<?> parentClass = parent.getCompileTimeClass(); if (parentClass != null) { if (requiredClass != null && !requiredClass.isAssignableFrom(parentClass)) { // mismatch; but this can occur (see ShadowAttributesType vs ShadowIdentifiersType in ShadowAssociationType) // but TODO maybe this is only a workaround and the problem is in the schema itself (?) return requiredClass; } else { return (Class<C>) parentClass; } } } } return requiredClass; } private C asContainerableInternal(Class<C> clazz) { if (clazz == null) { String elementName = getParent() != null ? String.valueOf(getParent().getElementName()) : String.valueOf(this); throw new SystemException("Unknown compile time class of container value of '" + elementName + "'."); } if (Modifier.isAbstract(clazz.getModifiers())) { throw new SystemException("Can't create instance of class '" + clazz.getSimpleName() + "', it's abstract."); } try { if (prismContext != null) { containerable = clazz.getConstructor(PrismContext.class).newInstance(prismContext); } else { containerable = clazz.newInstance(); } containerable.setupContainerValue(this); return containerable; } catch (SystemException ex) { throw ex; } catch (Exception ex) { throw new SystemException("Couldn't create jaxb object instance of '" + clazz + "': "+ex.getMessage(), ex); } } public Collection<QName> getPropertyNames() { Collection<QName> names = new HashSet<>(); for (PrismProperty<?> prop: getProperties()) { names.add(prop.getElementName()); } return names; } public <IV extends PrismValue,ID extends ItemDefinition> boolean add(Item<IV,ID> item) throws SchemaException { return add(item, true); } /** * Adds an item to a property container. * * @param item item to add. * @throws SchemaException * @throws IllegalArgumentException an attempt to add value that already exists */ public <IV extends PrismValue,ID extends ItemDefinition> boolean add(Item<IV,ID> item, boolean checkUniqueness) throws SchemaException { checkMutability(); if (item.getElementName() == null) { throw new IllegalArgumentException("Cannot add item without a name to value of container "+getParent()); } if (checkUniqueness && findItem(item.getElementName(), Item.class) != null) { throw new IllegalArgumentException("Item " + item.getElementName() + " is already present in " + this.getClass().getSimpleName()); } item.setParent(this); PrismContext prismContext = getPrismContext(); if (prismContext != null) { item.setPrismContext(prismContext); } if (getComplexTypeDefinition() != null && item.getDefinition() == null) { item.applyDefinition((ID)determineItemDefinition(item.getElementName(), getComplexTypeDefinition()), false); } if (items == null) { items = new ArrayList<>(); } return items.add(item); } /** * Merges the provided item into this item. The values are joined together. * Returns true if new item or value was added. */ public <IV extends PrismValue,ID extends ItemDefinition> boolean merge(Item<IV,ID> item) throws SchemaException { checkMutability(); Item<IV, ID> existingItem = findItem(item.getElementName(), Item.class); if (existingItem == null) { return add(item); } else { boolean changed = false; for (IV newVal: item.getValues()) { if (existingItem.add((IV) newVal.clone())) { changed = true; } } return changed; } } /** * Subtract the provided item from this item. The values of the provided item are deleted * from this item. * Returns true if this item was changed. */ public <IV extends PrismValue,ID extends ItemDefinition> boolean subtract(Item<IV,ID> item) throws SchemaException { checkMutability(); Item<IV,ID> existingItem = findItem(item.getElementName(), Item.class); if (existingItem == null) { return false; } else { boolean changed = false; for (IV newVal: item.getValues()) { if (existingItem.remove(newVal)) { changed = true; } } return changed; } } /** * Adds an item to a property container. Existing value will be replaced. * * @param item item to add. */ public <IV extends PrismValue,ID extends ItemDefinition> void addReplaceExisting(Item<IV,ID> item) throws SchemaException { checkMutability(); if (item == null) { return; } Item<IV,ID> existingItem = findItem(item.getElementName(), Item.class); if (existingItem != null && items != null) { items.remove(existingItem); existingItem.setParent(null); } add(item); } public <IV extends PrismValue,ID extends ItemDefinition> void remove(Item<IV,ID> item) { Validate.notNull(item, "Item must not be null."); checkMutability(); Item<IV,ID> existingItem = findItem(item.getElementName(), Item.class); if (existingItem != null && items != null) { items.remove(existingItem); existingItem.setParent(null); } } public void removeAll() { checkMutability(); if (items == null){ return; } Iterator<Item<?,?>> iterator = items.iterator(); while (iterator.hasNext()) { Item<?,?> item = iterator.next(); item.setParent(null); iterator.remove(); } } /** * Adds a collection of items to a property container. * * @param itemsToAdd items to add * @throws IllegalArgumentException an attempt to add value that already exists */ public void addAll(Collection<? extends Item<?,?>> itemsToAdd) throws SchemaException { for (Item<?,?> item : itemsToAdd) { add(item); } } /** * Adds a collection of items to a property container. Existing values will be replaced. * * @param itemsToAdd items to add */ public void addAllReplaceExisting(Collection<? extends Item<?,?>> itemsToAdd) throws SchemaException { checkMutability(); // Check for conflicts, remove conflicting values for (Item<?,?> item : itemsToAdd) { Item<?,?> existingItem = findItem(item.getElementName(), Item.class); if (existingItem != null && items != null) { items.remove(existingItem); } } addAll(itemsToAdd); } public <IV extends PrismValue,ID extends ItemDefinition> void replace(Item<IV,ID> oldItem, Item<IV,ID> newItem) throws SchemaException { remove(oldItem); add(newItem); } public void clear() { checkMutability(); if (items != null) { items.clear(); } } public boolean contains(Item item) { return items != null && items.contains(item); } public boolean contains(QName itemName) { return findItem(itemName) != null; } public static <C extends Containerable> boolean containsRealValue(Collection<PrismContainerValue<C>> cvalCollection, PrismContainerValue<C> cval) { for (PrismContainerValue<C> colVal: cvalCollection) { if (colVal.equalsRealValue(cval)) { return true; } } return false; } @Override public Object find(ItemPath path) { if (path == null || path.isEmpty()) { return this; } ItemPathSegment first = path.first(); if (!(first instanceof NameItemPathSegment)) { throw new IllegalArgumentException("Attempt to lookup item using a non-name path "+path+" in "+this); } QName subName = ((NameItemPathSegment)first).getName(); ItemPath rest = path.rest(); Item<?,?> subItem = findItem(subName); if (subItem == null) { return null; } return subItem.find(rest); } @Override public <IV extends PrismValue,ID extends ItemDefinition> PartiallyResolvedItem<IV,ID> findPartial(ItemPath path) { if (path == null || path.isEmpty()) { // Incomplete path return null; } ItemPathSegment first = path.first(); if (!(first instanceof NameItemPathSegment)) { throw new IllegalArgumentException("Attempt to lookup item using a non-name path "+path+" in "+this); } QName subName = ((NameItemPathSegment)first).getName(); ItemPath rest = path.rest(); Item<?,?> subItem = findItem(subName); if (subItem == null) { return null; } return subItem.findPartial(rest); } @SuppressWarnings("unchecked") public <X> PrismProperty<X> findProperty(QName propertyQName) { return findItem(propertyQName, PrismProperty.class); } public <X> PrismProperty<X> findProperty(ItemPath propertyPath) { return (PrismProperty) findItem(propertyPath); } /** * Finds a specific property in the container by definition. * <p/> * Returns null if nothing is found. * * @param propertyDefinition property definition to find. * @return found property or null */ public <X> PrismProperty<X> findProperty(PrismPropertyDefinition<X> propertyDefinition) { if (propertyDefinition == null) { throw new IllegalArgumentException("No property definition"); } return findProperty(propertyDefinition.getName()); } public <X extends Containerable> PrismContainer<X> findContainer(QName containerName) { return findItem(containerName, PrismContainer.class); } public PrismReference findReference(QName elementName) { return findItem(elementName, PrismReference.class); } public PrismReference findReferenceByCompositeObjectElementName(QName elementName) { if (items == null){ return null; } for (Item item: items) { if (item instanceof PrismReference) { PrismReference ref = (PrismReference)item; PrismReferenceDefinition refDef = ref.getDefinition(); if (refDef != null) { if (elementName.equals(refDef.getCompositeObjectElementName())) { return ref; } } } } return null; } public <IV extends PrismValue,ID extends ItemDefinition, I extends Item<IV,ID>> I findItem(QName itemName, Class<I> type) { try { return findCreateItem(itemName, type, null, false); } catch (SchemaException e) { // This should not happen throw new SystemException("Internal Error: "+e.getMessage(),e); } } public <IV extends PrismValue,ID extends ItemDefinition> Item<IV,ID> findItem(QName itemName) { try { return findCreateItem(itemName, Item.class, null, false); } catch (SchemaException e) { // This should not happen throw new SystemException("Internal Error: "+e.getMessage(),e); } } public <IV extends PrismValue,ID extends ItemDefinition> Item<IV,ID> findItem(ItemPath itemPath) { try { return findCreateItem(itemPath, Item.class, null, false); } catch (SchemaException e) { // This should not happen throw new SystemException("Internal Error: "+e.getMessage(),e); } } @SuppressWarnings("unchecked") <IV extends PrismValue,ID extends ItemDefinition, I extends Item<IV,ID>> I findCreateItem(QName itemName, Class<I> type, ID itemDefinition, boolean create) throws SchemaException { Item<IV,ID> item = findItemByQName(itemName); if (item != null) { if (type.isAssignableFrom(item.getClass())) { return (I) item; } else { if (create) { throw new IllegalStateException("The " + type.getSimpleName() + " cannot be created because " + item.getClass().getSimpleName() + " with the same name exists ("+item.getElementName()+")"); } else { return null; } } } if (create) { // todo treat unqualified names return createSubItem(itemName, type, itemDefinition); } else { return null; } } public <IV extends PrismValue,ID extends ItemDefinition, I extends Item<IV,ID>> I findItem(ItemDefinition itemDefinition, Class<I> type) { if (itemDefinition == null) { throw new IllegalArgumentException("No item definition"); } return findItem(itemDefinition.getName(), type); } public boolean containsItem(ItemPath propPath, boolean acceptEmptyItem) throws SchemaException { ItemPathSegment first = propPath.first(); if (!(first instanceof NameItemPathSegment)) { throw new IllegalArgumentException("Attempt to lookup item using a non-name path "+propPath+" in "+this); } QName subName = ((NameItemPathSegment)first).getName(); ItemPath rest = propPath.rest(); Item item = findItemByQName(subName); if (item != null) { if (rest.isEmpty()) { return (acceptEmptyItem || !item.isEmpty()); } else { // Go deeper if (item instanceof PrismContainer) { return ((PrismContainer<?>)item).containsItem(rest, acceptEmptyItem); } else { return (acceptEmptyItem || !item.isEmpty()); } } } return false; } // Expects that "self" path is NOT present in propPath @SuppressWarnings("unchecked") <IV extends PrismValue,ID extends ItemDefinition, I extends Item<IV,ID>> I findCreateItem(ItemPath propPath, Class<I> type, ID itemDefinition, boolean create) throws SchemaException { ItemPathSegment first = propPath.first(); if (!(first instanceof NameItemPathSegment)) { throw new IllegalArgumentException("Attempt to lookup item using a non-name path "+propPath+" in "+this); } QName subName = ((NameItemPathSegment)first).getName(); ItemPath rest = propPath.rest(); I item = (I) findItemByQName(subName); if (item != null) { if (rest.isEmpty()) { if (type.isAssignableFrom(item.getClass())) { return (I)item; } else { if (create) { throw new SchemaException("The " + type.getSimpleName() + " cannot be created because " + item.getClass().getSimpleName() + " with the same name exists ("+item.getElementName()+")"); } else { return null; } } } else { // Go deeper if (item instanceof PrismContainer) { return ((PrismContainer<?>)item).findCreateItem(rest, type, itemDefinition, create); } else { if (create) { throw new SchemaException("The " + type.getSimpleName() + " cannot be created because " + item.getClass().getSimpleName() + " with the same name exists ("+item.getElementName()+")"); } else { // Return the item for non-container even if the path is non-empty // FIXME: This is not the best solution but it is needed to be able to look inside properties // such as PolyString if (type.isAssignableFrom(item.getClass())) { return item; } else { return null; } } } } } if (create) { // todo treat unqualified names if (rest.isEmpty()) { return createSubItem(subName, type, itemDefinition); } else { // Go deeper PrismContainer<?> subItem = createSubItem(subName, PrismContainer.class, null); return subItem.findCreateItem(rest, type, itemDefinition, create); } } else { return null; } } private <IV extends PrismValue,ID extends ItemDefinition> Item<IV,ID> findItemByQName(QName subName) throws SchemaException { if (items == null) { return null; } Item<IV,ID> matching = null; for (Item<?,?> item : items) { if (QNameUtil.match(subName, item.getElementName())) { if (matching != null) { String containerName = getParent() != null ? DebugUtil.formatElementName(getParent().getElementName()) : ""; throw new SchemaException("More than one items matching " + subName + " in container " + containerName); } else { matching = (Item<IV, ID>) item; } } } return matching; } public <IV extends PrismValue,ID extends ItemDefinition,I extends Item<IV,ID>> I createDetachedSubItem(QName name, Class<I> type, ID itemDefinition, boolean immutable) throws SchemaException { I newItem = createDetachedNewItemInternal(name, type, itemDefinition); if (immutable) { newItem.setImmutable(true); } return newItem; } private <IV extends PrismValue,ID extends ItemDefinition,I extends Item<IV,ID>> I createSubItem(QName name, Class<I> type, ID itemDefinition) throws SchemaException { checkMutability(); I newItem = createDetachedNewItemInternal(name, type, itemDefinition); add(newItem); return newItem; } private <IV extends PrismValue,ID extends ItemDefinition,I extends Item<IV,ID>> I createDetachedNewItemInternal(QName name, Class<I> type, ID itemDefinition) throws SchemaException { I newItem; if (itemDefinition == null) { ComplexTypeDefinition ctd = getComplexTypeDefinition(); itemDefinition = determineItemDefinition(name, ctd); if (ctd != null && itemDefinition == null) { throw new SchemaException("No definition for item "+name+" in "+getParent()); } } if (itemDefinition != null) { if (StringUtils.isNotBlank(name.getNamespaceURI())){ newItem = (I) itemDefinition.instantiate(name); } else { QName computed = new QName(itemDefinition.getNamespace(), name.getLocalPart()); newItem = (I) itemDefinition.instantiate(computed); } if (newItem instanceof PrismObject) { throw new IllegalStateException("PrismObject instantiated as a subItem in "+this+" from definition "+itemDefinition); } } else { newItem = Item.createNewDefinitionlessItem(name, type, prismContext); if (newItem instanceof PrismObject) { throw new IllegalStateException("PrismObject instantiated as a subItem in "+this+" as definitionless instance of class "+type); } } if (type.isAssignableFrom(newItem.getClass())) { return newItem; } else { throw new IllegalStateException("The " + type.getSimpleName() + " cannot be created because the item should be of type " + newItem.getClass().getSimpleName() + " ("+newItem.getElementName()+")"); } } public <T extends Containerable> PrismContainer<T> findOrCreateContainer(QName containerName) throws SchemaException { return findCreateItem(containerName, PrismContainer.class, null, true); } public PrismReference findOrCreateReference(QName referenceName) throws SchemaException { return findCreateItem(referenceName, PrismReference.class, null, true); } public <IV extends PrismValue,ID extends ItemDefinition> Item<IV,ID> findOrCreateItem(QName containerName) throws SchemaException { return findCreateItem(containerName, Item.class, null, true); } public <IV extends PrismValue,ID extends ItemDefinition,I extends Item<IV,ID>> I findOrCreateItem(QName containerName, Class<I> type) throws SchemaException { return findCreateItem(containerName, type, null, true); } public <IV extends PrismValue,ID extends ItemDefinition,I extends Item<IV,ID>> I findOrCreateItem(ItemPath path, Class<I> type, ID definition) throws SchemaException { return findCreateItem(path, type, definition, true); } public <X> PrismProperty<X> findOrCreateProperty(QName propertyQName) throws SchemaException { PrismProperty<X> property = findItem(propertyQName, PrismProperty.class); if (property != null) { return property; } return createProperty(propertyQName); } public <X> PrismProperty<X> findOrCreateProperty(ItemPath propertyPath) throws SchemaException { return findOrCreateItem(propertyPath, PrismProperty.class, null); } public <X> PrismProperty<X> findOrCreateProperty(PrismPropertyDefinition propertyDef) throws SchemaException { PrismProperty<X> property = findItem(propertyDef.getName(), PrismProperty.class); if (property != null) { return property; } return createProperty(propertyDef); } public <X> PrismProperty<X> createProperty(QName propertyName) throws SchemaException { checkMutability(); PrismPropertyDefinition propertyDefinition = determineItemDefinition(propertyName, getComplexTypeDefinition()); if (propertyDefinition == null) { // container has definition, but there is no property definition. This is either runtime schema // or an error if (getParent() != null && getDefinition() != null && !getDefinition().isRuntimeSchema()) { // TODO clean this up throw new IllegalArgumentException("No definition for property "+propertyName+" in "+complexTypeDefinition); } } PrismProperty<X> property; if (propertyDefinition == null) { property = new PrismProperty<X>(propertyName, prismContext); // Definitionless } else { property = propertyDefinition.instantiate(); } add(property, false); return property; } public <X> PrismProperty<X> createProperty(PrismPropertyDefinition propertyDefinition) throws SchemaException { PrismProperty<X> property = propertyDefinition.instantiate(); add(property); return property; } public void removeProperty(QName propertyName) { removeProperty(new ItemPath(propertyName)); } public void removeProperty(ItemPath propertyPath) { removeItem(propertyPath, PrismProperty.class); } public void removeContainer(QName containerName) { removeContainer(new ItemPath(containerName)); } public void removeContainer(ItemPath itemPath) { removeItem(itemPath, PrismContainer.class); } public void removeReference(QName name) { removeReference(new ItemPath(name)); } public void removeReference(ItemPath path) { removeItem(path, PrismReference.class); } // Expects that "self" path is NOT present in propPath <IV extends PrismValue,ID extends ItemDefinition,I extends Item<IV,ID>> void removeItem(ItemPath propPath, Class<I> itemType) { checkMutability(); if (items == null){ return; } ItemPathSegment first = propPath.first(); if (!(first instanceof NameItemPathSegment)) { throw new IllegalArgumentException("Attempt to remove item using a non-name path "+propPath+" in "+this); } QName subName = ((NameItemPathSegment)first).getName(); ItemPath rest = propPath.rest(); Iterator<Item<?,?>> itemsIterator = items.iterator(); while(itemsIterator.hasNext()) { Item<?,?> item = itemsIterator.next(); if (subName.equals(item.getElementName())) { if (!rest.isEmpty() && item instanceof PrismContainer) { ((PrismContainer<?>)item).removeItem(propPath, itemType); return; } else { if (itemType.isAssignableFrom(item.getClass())) { itemsIterator.remove(); } else { throw new IllegalArgumentException("Attempt to remove item "+subName+" from "+this+ " of type "+itemType+" while the existing item is of incompatible type "+item.getClass()); } } } } } public void setPropertyRealValue(QName propertyName, Object realValue, PrismContext prismContext) throws SchemaException { checkMutability(); PrismProperty<?> property = findOrCreateProperty(propertyName); property.setRealValue(realValue); if (property.getPrismContext() == null) { property.setPrismContext(prismContext); } } public <T> T getPropertyRealValue(QName propertyName, Class<T> type) { PrismProperty<T> property = findProperty(propertyName); if (property == null) { // when using sql repo, even non-existing properties do not have 'null' here return null; } return property.getRealValue(type); } @Override public void recompute(PrismContext prismContext) { // Nothing to do. The subitems should be already recomputed as they are added to this container. } @Override public void accept(Visitor visitor) { super.accept(visitor); if (items != null) { for (Item<?,?> item : getItems()) { item.accept(visitor); } } } @Override public void accept(Visitor visitor, ItemPath path, boolean recursive) { if (path == null || path.isEmpty()) { // We are at the end of path, continue with regular visits all the way to the bottom if (recursive) { accept(visitor); } else { visitor.visit(this); } } else { ItemPathSegment first = path.first(); if (!(first instanceof NameItemPathSegment)) { throw new IllegalArgumentException("Attempt to lookup item using a non-name path "+path+" in "+this); } QName subName = ((NameItemPathSegment)first).getName(); ItemPath rest = path.rest(); if (items != null) { for (Item<?,?> item : items) { // todo unqualified names! if (first.isWildcard() || subName.equals(item.getElementName())) { item.accept(visitor, rest, recursive); } } } } } public boolean hasCompleteDefinition() { if (items != null) { for (Item<?,?> item : getItems()) { if (!item.hasCompleteDefinition()) { return false; } } } return true; } @Override public boolean representsSameValue(PrismValue other, boolean lax) { if (other instanceof PrismContainerValue) { return representsSameValue((PrismContainerValue<C>)other, lax); } else { return false; } } @SuppressWarnings("Duplicates") private boolean representsSameValue(PrismContainerValue<C> other, boolean lax) { if (lax && getParent() != null) { PrismContainerDefinition definition = getDefinition(); if (definition != null) { if (definition.isSingleValue()) { // There is only one value, therefore it always represents the same thing return true; } } } if (lax && other.getParent() != null) { PrismContainerDefinition definition = other.getDefinition(); if (definition != null) { if (definition.isSingleValue()) { // There is only one value, therefore it always represents the same thing return true; } } } if (this.getId() != null && other.getId() != null) { return this.getId().equals(other.getId()); } return false; } @Override void diffMatchingRepresentation(PrismValue otherValue, Collection<? extends ItemDelta> deltas, boolean ignoreMetadata, boolean isLiteral) { if (otherValue instanceof PrismContainerValue) { diffRepresentation((PrismContainerValue)otherValue, deltas, ignoreMetadata, isLiteral); } else { throw new IllegalStateException("Comparing incompatible values "+this+" - "+otherValue); } } void diffRepresentation(PrismContainerValue<C> otherValue, Collection<? extends ItemDelta> deltas, boolean ignoreMetadata, boolean isLiteral) { diffItems(this, otherValue, deltas, ignoreMetadata, isLiteral); } @Override public boolean isRaw() { return false; } public boolean addRawElement(Object element) throws SchemaException { checkMutability(); PrismContainerDefinition<C> definition = getDefinition(); if (definition == null) { throw new UnsupportedOperationException("Definition-less containers are not supported any more."); } else { // We have definition here, we can parse it right now Item<?,?> subitem = parseRawElement(element, definition); return merge(subitem); } } public boolean deleteRawElement(Object element) throws SchemaException { checkMutability(); PrismContainerDefinition<C> definition = getDefinition(); if (definition == null) { throw new UnsupportedOperationException("Definition-less containers are not supported any more."); } else { // We have definition here, we can parse it right now Item<?,?> subitem = parseRawElement(element, definition); return subtract(subitem); } } public boolean removeRawElement(Object element) { checkMutability(); throw new UnsupportedOperationException("Definition-less containers are not supported any more."); } private <IV extends PrismValue,ID extends ItemDefinition> Item<IV,ID> parseRawElement(Object element, PrismContainerDefinition<C> definition) throws SchemaException { JaxbDomHack jaxbDomHack = definition.getPrismContext().getJaxbDomHack(); return jaxbDomHack.parseRawElement(element, definition); } private PrismContainerValue<C> parseRawElementsToNewValue(PrismContainerValue<C> origCVal, PrismContainerValue<C> definitionSource) throws SchemaException { throw new UnsupportedOperationException("Definition-less containers are not supported any more."); } @SuppressWarnings({ "rawtypes", "unchecked" }) void diffItems(PrismContainerValue<C> thisValue, PrismContainerValue<C> other, Collection<? extends ItemDelta> deltas, boolean ignoreMetadata, boolean isLiteral) { if (thisValue.getItems() != null) { for (Item<?,?> thisItem: thisValue.getItems()) { Item otherItem = other.findItem(thisItem.getElementName()); if (!isLiteral) { ItemDefinition itemDef = thisItem.getDefinition(); if (itemDef == null && other.getDefinition() != null) { itemDef = other.getDefinition().findItemDefinition(thisItem.getElementName()); } if (isOperationalOnly(thisItem, itemDef) && (otherItem == null || isOperationalOnly(otherItem, itemDef))) { continue; } } // The "delete" delta will also result from the following diff thisItem.diffInternal(otherItem, deltas, ignoreMetadata, isLiteral); } } if (other.getItems() != null) { for (Item otherItem: other.getItems()) { Item thisItem = thisValue.findItem(otherItem.getElementName()); if (thisItem != null) { // Already processed in previous loop continue; } if (!isLiteral) { ItemDefinition itemDef = otherItem.getDefinition(); if (itemDef == null && thisValue.getDefinition() != null) { itemDef = thisValue.getDefinition().findItemDefinition(otherItem.getElementName()); } if (isOperationalOnly(otherItem, itemDef)) { continue; } } // Other has an item that we don't have, this must be an add ItemDelta itemDelta = otherItem.createDelta(); itemDelta.addValuesToAdd(otherItem.getClonedValues()); if (!itemDelta.isEmpty()) { ((Collection)deltas).add(itemDelta); } } } } private boolean isOperationalOnly(Item item, ItemDefinition itemDef) { if (itemDef != null && itemDef.isOperational()) { return true; } if (item.isEmpty()) { return false; } if (!(item instanceof PrismContainer)) { return false; } PrismContainer<?> container = (PrismContainer)item; for (PrismContainerValue<?> cval: container.getValues()) { if (cval != null) { List<Item<?, ?>> subitems = cval.getItems(); if (subitems != null) { for (Item<?, ?> subitem: subitems) { ItemDefinition subItemDef = subitem.getDefinition(); if (subItemDef == null && itemDef != null) { subItemDef = ((PrismContainerDefinition)itemDef).findItemDefinition(subitem.getElementName()); } if (subItemDef == null) { return false; } if (!subItemDef.isOperational()) { return false; } } } } } return true; } @Override protected PrismContainerDefinition<C> getDefinition() { return (PrismContainerDefinition<C>) super.getDefinition(); } @Override public void applyDefinition(ItemDefinition definition, boolean force) throws SchemaException { checkMutability(); if (!(definition instanceof PrismContainerDefinition)) { throw new IllegalArgumentException("Cannot apply "+definition+" to container " + this); } applyDefinition((PrismContainerDefinition<C>)definition, force); } public void applyDefinition(@NotNull PrismContainerDefinition<C> containerDef, boolean force) throws SchemaException { checkMutability(); if (complexTypeDefinition != null && !force) { return; // there's a definition already } replaceComplexTypeDefinition(containerDef.getComplexTypeDefinition()); // we need to continue even if CTD is null or 'any' - e.g. to resolve definitions within object extension if (items != null) { for (Item item : items) { if (item.getDefinition() != null && !force) { // Item has a definition already, no need to apply it continue; } ItemDefinition itemDefinition = determineItemDefinition(item.getElementName(), complexTypeDefinition); if (itemDefinition == null && item.getDefinition() != null && item.getDefinition().isDynamic()) { // We will not apply the null definition here. The item has a dynamic definition that we don't // want to destroy as it cannot be reconstructed later. } else { item.applyDefinition(itemDefinition, force); } } } } /** * This method can both return null and throws exception. It returns null in case there is no definition * but it is OK (e.g. runtime schema). It throws exception if there is no definition and it is not OK. */ private <ID extends ItemDefinition> ID determineItemDefinition(QName itemName, @Nullable ComplexTypeDefinition ctd) throws SchemaException { ID itemDefinition = ctd != null ? ctd.findItemDefinition(itemName) : null; if (itemDefinition != null) { return itemDefinition; } if (ctd == null || ctd.isXsdAnyMarker() || ctd.isRuntimeSchema()) { // If we have prism context, try to locate global definition. But even if that is not // found it is still OK. This is runtime container. We tolerate quite a lot here. PrismContext prismContext = getPrismContext(); if (prismContext != null) { return (ID) prismContext.getSchemaRegistry().resolveGlobalItemDefinition(itemName, ctd); } else { return null; } } else { throw new SchemaException("No definition for item " + itemName + " in " + getParent()); } } @Override public void revive(PrismContext prismContext) throws SchemaException { if (this.prismContext == null) { this.prismContext = prismContext; } super.revive(prismContext); if (items != null) { for (Item<?,?> item: items) { item.revive(prismContext); } } } @Override public boolean isEmpty() { if (id != null) { return false; } if (items == null) { return true; } return items.isEmpty(); } @Override public void normalize() { checkMutability(); if (items != null) { for (Item<?, ?> item : items) { item.normalize(); } } } @Override public void checkConsistenceInternal(Itemable rootItem, boolean requireDefinitions, boolean prohibitRaw, ConsistencyCheckScope scope) { ItemPath myPath = getPath(); if (getDefinition() == null) { throw new IllegalStateException("Definition-less container value " + this +" ("+myPath+" in "+rootItem+")"); } if (items != null) { for (Item<?,?> item: items) { if (scope.isThorough()) { if (item == null) { throw new IllegalStateException("Null item in container value "+this+" ("+myPath+" in "+rootItem+")"); } if (item.getParent() == null) { throw new IllegalStateException("No parent for item "+item+" in container value "+this+" ("+myPath+" in "+rootItem+")"); } if (item.getParent() != this) { throw new IllegalStateException("Wrong parent for item "+item+" in container value "+this+" ("+myPath+" in "+rootItem+"), " + "bad parent: "+ item.getParent()); } } item.checkConsistenceInternal(rootItem, requireDefinitions, prohibitRaw, scope); } } } public void assertDefinitions(String sourceDescription) throws SchemaException { assertDefinitions(false, sourceDescription); } public void assertDefinitions(boolean tolerateRaw, String sourceDescription) throws SchemaException { if (getItems() == null){ return; } for (Item<?,?> item: getItems()) { item.assertDefinitions(tolerateRaw, "value("+getId()+") in "+sourceDescription); } } public PrismContainerValue<C> clone() { // TODO resolve also the definition? PrismContainerValue<C> clone = new PrismContainerValue<>(getOriginType(), getOriginObject(), getParent(), getId(), this.complexTypeDefinition, this.prismContext); copyValues(clone); return clone; } protected void copyValues(PrismContainerValue<C> clone) { super.copyValues(clone); clone.id = this.id; if (this.items != null) { for (Item<?,?> item : this.items) { Item<?,?> clonedItem = item.clone(); clonedItem.setParent(clone); if (clone.items == null) { clone.items = new ArrayList<>(this.items.size()); } clone.items.add(clonedItem); } } } protected void deepCloneDefinition(boolean ultraDeep, PrismContainerDefinition<C> clonedContainerDef) { if (items != null) { for (Item<?,?> item: items) { deepCloneDefinitionItem(item, ultraDeep, clonedContainerDef); } } } private <IV extends PrismValue,ID extends ItemDefinition, I extends Item<IV,ID>> void deepCloneDefinitionItem(I item, boolean ultraDeep, PrismContainerDefinition<C> clonedContainerDef) { PrismContainerDefinition<C> oldContainerDef = getDefinition(); QName itemName = item.getElementName(); ID oldItemDefFromContainer = oldContainerDef.findItemDefinition(itemName); ID oldItemDef = item.getDefinition(); ID clonedItemDef; if (oldItemDefFromContainer == oldItemDef) { clonedItemDef = clonedContainerDef.findItemDefinition(itemName); } else { clonedItemDef = (ID) oldItemDef.deepClone(ultraDeep); } // special treatment of CTD (we must not simply overwrite it with clonedPCD.CTD!) PrismContainerable parent = getParent(); if (parent != null && complexTypeDefinition != null) { if (complexTypeDefinition == parent.getComplexTypeDefinition()) { replaceComplexTypeDefinition(clonedContainerDef.getComplexTypeDefinition()); } else { replaceComplexTypeDefinition(complexTypeDefinition.deepClone(ultraDeep ? null : new HashMap<>() )); // OK? } } item.propagateDeepCloneDefinition(ultraDeep, clonedItemDef); // propagate to items in values item.setDefinition(clonedItemDef); // sets CTD in values only if null! } @Override public boolean equalsComplex(PrismValue other, boolean ignoreMetadata, boolean isLiteral) { if (other == null || !(other instanceof PrismContainerValue<?>)) { return false; } return equalsComplex((PrismContainerValue<?>)other, ignoreMetadata, isLiteral); } public boolean equalsComplex(PrismContainerValue<?> other, boolean ignoreMetadata, boolean isLiteral) { if (!super.equalsComplex(other, ignoreMetadata, isLiteral)) { return false; } if (!ignoreMetadata) { if (this.id == null) { if (other.id != null) return false; } else if (!this.id.equals(other.id)) return false; } if (this.items == null) { if (other.items != null) return false; } else if (!this.equalsItems(this, (PrismContainerValue<C>) other, ignoreMetadata, isLiteral)) { return false; } return true; } boolean equalsItems(PrismContainerValue<C> other, boolean ignoreMetadata, boolean isLiteral) { return equalsItems(this, other, ignoreMetadata, isLiteral); } boolean equalsItems(PrismContainerValue<C> thisValue, PrismContainerValue<C> other, boolean ignoreMetadata, boolean isLiteral) { Collection<? extends ItemDelta<?,?>> deltas = new ArrayList<ItemDelta<?,?>>(); // The EMPTY_PATH is a lie. We don't really care if the returned deltas have correct path or not // we only care whether some deltas are returned or not. diffItems(thisValue, other, deltas, ignoreMetadata, isLiteral); return deltas.isEmpty(); } public boolean equivalent(PrismContainerValue<?> other) { return equalsRealValue(other); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; PrismContainerValue<?> other = (PrismContainerValue<?>) obj; return equalsComplex(other, false, false); } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((items == null) ? 0 : MiscUtil.unorderedCollectionHashcode(items)); return result; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("PCV("); sb.append(getId()); sb.append("):"); sb.append(getItems()); return sb.toString(); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); boolean wasIndent = false; if (DebugUtil.isDetailedDebugDump()) { DebugUtil.indentDebugDump(sb, indent); wasIndent = true; detailedDebugDumpStart(sb); } boolean multivalue = true; PrismContainerable<C> parent = getParent(); if (parent != null && parent.getDefinition() != null) { multivalue = parent.getDefinition().isMultiValue(); } Long id = getId(); if (multivalue || id != null || DebugUtil.isDetailedDebugDump()) { if (!wasIndent) { DebugUtil.indentDebugDump(sb, indent); wasIndent = true; } debugDumpIdentifiers(sb); } appendOriginDump(sb); List<Item<?,?>> items = getItems(); if (items == null || items.isEmpty()) { DebugUtil.indentDebugDump(sb, indent + 1); sb.append("(no items)"); } else { Iterator<Item<?,?>> i = getItems().iterator(); if (wasIndent && i.hasNext()) { sb.append("\n"); } while (i.hasNext()) { Item<?,?> item = i.next(); sb.append(item.debugDump(indent + 1)); if (i.hasNext()) { sb.append("\n"); } } } return sb.toString(); } protected void debugDumpIdentifiers(StringBuilder sb) { sb.append("id=").append(PrettyPrinter.prettyPrint(getId())); } protected void detailedDebugDumpStart(StringBuilder sb) { sb.append("PCV").append(": "); } @Override public boolean match(PrismValue otherValue) { return equalsRealValue(otherValue); } @Override public String toHumanReadableString() { return "id="+id+": "+items.size()+" items"; } // copies the definition from original to aClone (created outside of this method) // it has to (artifically) create a parent PrismContainer to hold the definition // // without having a definition, such containers cannot be serialized using // PrismJaxbProcessor.marshalContainerableToString (without definition, there is // no information on corresponding element name) // // todo review usefulness and appropriateness of this method and its placement @Deprecated public static void copyDefinition(Containerable aClone, Containerable original, PrismContext prismContext) { try { Validate.notNull(original.asPrismContainerValue().getParent(), "original PrismContainerValue has no parent"); ComplexTypeDefinition definition = original.asPrismContainerValue().getComplexTypeDefinition(); Validate.notNull(definition, "original PrismContainer definition is null"); PrismContainer<?> aCloneParent = prismContext.getSchemaRegistry() .findContainerDefinitionByCompileTimeClass((Class<? extends Containerable>) definition.getCompileTimeClass()) .instantiate(); aCloneParent.add(aClone.asPrismContainerValue()); } catch (SchemaException e) { throw new SystemException("Unexpected SchemaException when copying definition from original object to its clone", e); } } public QName getTypeName() { return getComplexTypeDefinition() != null ? getComplexTypeDefinition().getTypeName() : null; } @Nullable public ComplexTypeDefinition getComplexTypeDefinition() { if (complexTypeDefinition == null) { complexTypeDefinition = determineComplexTypeDefinition(); } return complexTypeDefinition; } // will correctly work only if argument is not null (otherwise the CTD will be determined on next call to getCTD) void replaceComplexTypeDefinition(ComplexTypeDefinition complexTypeDefinition) { // if (this.complexTypeDefinition != null && complexTypeDefinition != null && !this.complexTypeDefinition.getTypeName().equals(complexTypeDefinition.getTypeName())) { // System.out.println("Dangerous!"); // } this.complexTypeDefinition = complexTypeDefinition; } private ComplexTypeDefinition determineComplexTypeDefinition() { PrismContainerable<C> parent = getParent(); ComplexTypeDefinition parentCTD = parent != null && parent.getDefinition() != null ? parent.getDefinition().getComplexTypeDefinition() : null; if (containerable == null) { return parentCTD; } if (prismContext == null) { // check if parentCTD matches containerable if (parentCTD != null && containerable.getClass().equals(parentCTD.getCompileTimeClass())) { return parentCTD; } else { //throw new IllegalStateException("Cannot determine complexTypeDefinition for PrismContainerValue because prismContext is missing; PCV = " + this); return null; } } ComplexTypeDefinition def = prismContext.getSchemaRegistry().findComplexTypeDefinitionByCompileTimeClass(containerable.getClass()); return def; // may be null at this place } public static <T extends Containerable> List<PrismContainerValue<T>> toPcvList(List<T> beans) { List<PrismContainerValue<T>> rv = new ArrayList<>(beans.size()); for (T bean : beans) { rv.add(bean.asPrismContainerValue()); } return rv; } @Override public void setImmutable(boolean immutable) { super.setImmutable(immutable); if (items != null) { for (Item item : items) { item.setImmutable(immutable); } } } @Override public Class<?> getRealClass() { if (containerable != null) { return containerable.getClass(); } return resolveClass(null); } @SuppressWarnings("unchecked") @Nullable @Override public <T> T getRealValue() { return (T) asContainerable(); } /** * Returns a single-valued container (with a single-valued definition) holding just this value. * @param itemName Item name for newly-created container. * @return */ public PrismContainer<C> asSingleValuedContainer(@NotNull QName itemName) throws SchemaException { PrismContext prismContext = getPrismContext(); Validate.notNull(prismContext, "Prism context is null"); PrismContainerDefinitionImpl<C> definition = new PrismContainerDefinitionImpl<>(itemName, getComplexTypeDefinition(), prismContext); definition.setMaxOccurs(1); PrismContainer<C> pc = definition.instantiate(); pc.add(clone()); return pc; } // EXPERIMENTAL. TODO write some tests // BEWARE, it expects that definitions for items are present. Otherwise definition-less single valued items will get overwritten. @SuppressWarnings("unchecked") public void mergeContent(PrismContainerValue<?> other, List<QName> overwrite) throws SchemaException { List<QName> remainingToOverwrite = new ArrayList<>(overwrite); if (other.getItems() != null) { for (Item<?, ?> otherItem : other.getItems()) { Item<?, ?> existingItem = findItem(otherItem.elementName); if (QNameUtil.remove(remainingToOverwrite, otherItem.getElementName()) || existingItem != null && existingItem.isSingleValue()) { remove(existingItem); } merge(otherItem.clone()); } } remainingToOverwrite.forEach(name -> removeItem(new ItemPath(name), Item.class)); } @Override public PrismContainerValue<?> getRootValue() { return (PrismContainerValue) super.getRootValue(); } public static <C extends Containerable> List<PrismContainerValue<C>> asPrismContainerValues(List<C> containerables) { return containerables.stream().map(c -> (PrismContainerValue<C>) c.asPrismContainerValue()).collect(Collectors.toList()); } public static <C extends Containerable> List<C> asContainerables(List<PrismContainerValue<C>> pcvs) { return pcvs.stream().map(c -> c.asContainerable()).collect(Collectors.toList()); } /** * Set origin type to all values and subvalues */ public void setOriginTypeRecursive(final OriginType originType) { accept((visitable) -> { if (visitable instanceof PrismValue) { ((PrismValue)visitable).setOriginType(originType); } }); } }