/* * 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 com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.PropertyDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.xnode.ListXNode; import com.evolveum.midpoint.prism.xnode.PrimitiveXNode; import com.evolveum.midpoint.prism.xnode.RootXNode; import com.evolveum.midpoint.prism.xnode.XNode; 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.exception.SchemaException; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import org.apache.commons.lang.Validate; import org.jetbrains.annotations.NotNull; import javax.xml.namespace.QName; import java.lang.reflect.Array; import java.util.*; /** * Property is a specific characteristic of an object. It may be considered * object "attribute" or "field". For example User has fullName property that * contains string value of user's full name. * <p/> * Properties may be single-valued or multi-valued * <p/> * Properties may contain primitive types or complex types (defined by XSD * schema) * <p/> * Property values are unordered, implementation may change the order of values * <p/> * Duplicate values of properties should be silently removed by implementations, * but clients must be able tolerate presence of duplicate values. * <p/> * Operations that modify the objects work with the granularity of properties. * They add/remove/replace the values of properties, but do not "see" inside the * property. * <p/> * Property is mutable. * * @author Radovan Semancik */ public class PrismProperty<T> extends Item<PrismPropertyValue<T>,PrismPropertyDefinition<T>> { private static final long serialVersionUID = 6843901365945935660L; private static final Trace LOGGER = TraceManager.getTrace(PrismProperty.class); private static final int MAX_SINGLELINE_LEN = 40; public PrismProperty(QName name) { super(name); } public PrismProperty(QName name, PrismContext prismContext) { super(name, prismContext); } protected PrismProperty(QName name, PrismPropertyDefinition<T> definition, PrismContext prismContext) { super(name, definition, prismContext); } /** * Returns applicable property definition. * <p/> * May return null if no definition is applicable or the definition is not * know. * * @return applicable property definition */ public PrismPropertyDefinition<T> getDefinition() { return definition; } /** * Sets applicable property definition. * * TODO remove (method in Item is sufficient) * @param definition the definition to set */ public void setDefinition(PrismPropertyDefinition<T> definition) { checkMutability(); this.definition = definition; } public PrismPropertyValue<T> getValue() { if (!isSingleValue()) { throw new IllegalStateException("Attempt to get single value from property " + getElementName() + " with multiple values"); } List<PrismPropertyValue<T>> values = getValues(); if (values.isEmpty()) { return null; } return values.get(0); } /** * Type override, also for compatibility. */ public <X> List<PrismPropertyValue<X>> getValues(Class<X> type) { return (List) getValues(); } @Override public Collection<T> getRealValues() { Collection<T> realValues = new ArrayList<T>(getValues().size()); for (PrismPropertyValue<T> pValue: getValues()) { realValues.add(pValue.getValue()); } return realValues; } /** * Type override, also for compatibility. */ public <X> Collection<X> getRealValues(Class<X> type) { Collection<X> realValues = new ArrayList<X>(getValues().size()); for (PrismPropertyValue<T> pValue: getValues()) { realValues.add((X) pValue.getValue()); } return realValues; } public T getAnyRealValue() { Collection<T> values = getRealValues(); if (values.isEmpty()) { return null; } return values.iterator().next(); } public PrismPropertyValue<T> getAnyValue() { Collection<PrismPropertyValue<T>> values = getValues(); if (values.isEmpty()) { return null; } return values.iterator().next(); } @Override public T getRealValue() { if (getValue() == null) { return null; } return getValue().getValue(); } /** * Type override, also for compatibility. */ public <X> X getRealValue(Class<X> type) { if (getValue() == null) { return null; } X value = (X) getValue().getValue(); if (value == null) { return null; } if (type.isAssignableFrom(value.getClass())) { return (X)value; } else { throw new ClassCastException("Cannot cast value of property "+ getElementName()+" which is of type "+value.getClass()+" to "+type); } } /** * Type override, also for compatibility. */ public <X> X[] getRealValuesArray(Class<X> type) { X[] valuesArrary = (X[]) Array.newInstance(type, getValues().size()); for (int j = 0; j < getValues().size(); ++j) { Object avalue = getValues().get(j).getValue(); Array.set(valuesArrary, j, avalue); } return valuesArrary; } /** * Type override, also for compatibility. */ public <X> PrismPropertyValue<X> getValue(Class<X> type) { if (getDefinition() != null) { if (getDefinition().isMultiValue()) { throw new IllegalStateException("Attempt to get single value from property " + elementName + " with multiple values"); } } if (getValues().size() > 1) { throw new IllegalStateException("Attempt to get single value from property " + elementName + " with multiple values"); } if (getValues().isEmpty()) { return null; } PrismPropertyValue<X> o = (PrismPropertyValue<X>) getValues().iterator().next(); return o; } /** * Means as a short-hand for setting just a value for single-valued attributes. * Will remove all existing values. */ public void setValue(PrismPropertyValue<T> value) { clear(); addValue(value); } public void setRealValue(Object realValue) { if (realValue == null) { // Just make sure there are no values clear(); } else { setValue(new PrismPropertyValue(realValue)); } } public void addValues(Collection<PrismPropertyValue<T>> pValuesToAdd) { if (pValuesToAdd == null) { return; } for (PrismPropertyValue<T> pValue: pValuesToAdd) { addValue(pValue); } } public void addValue(PrismPropertyValue<T> pValueToAdd) { checkMutability(); pValueToAdd.checkValue(); Iterator<PrismPropertyValue<T>> iterator = getValues().iterator(); while (iterator.hasNext()) { PrismPropertyValue<T> pValue = iterator.next(); if (pValue.equalsRealValue(pValueToAdd)) { LOGGER.warn("Adding value to property "+ getElementName()+" that already exists (overwriting), value: "+pValueToAdd); iterator.remove(); } } pValueToAdd.setParent(this); pValueToAdd.recompute(); getValues().add(pValueToAdd); } public void addRealValue(T valueToAdd) { PrismPropertyValue<T> pval = new PrismPropertyValue<T>(valueToAdd); addValue(pval); } public boolean deleteValues(Collection<PrismPropertyValue<T>> pValuesToDelete) { checkMutability(); boolean changed = false; for (PrismPropertyValue<T> pValue: pValuesToDelete) { if (!changed) { changed = deleteValue(pValue); } else { deleteValue(pValue); } } return changed; } public boolean deleteValue(PrismPropertyValue<T> pValueToDelete) { checkMutability(); Iterator<PrismPropertyValue<T>> iterator = getValues().iterator(); boolean found = false; while (iterator.hasNext()) { PrismPropertyValue<T> pValue = iterator.next(); if (pValue.equalsRealValue(pValueToDelete)) { iterator.remove(); pValue.setParent(null); found = true; } } if (!found) { LOGGER.warn("Deleting value of property "+ getElementName()+" that does not exist (skipping), value: "+pValueToDelete); } return found; } public void replaceValues(Collection<PrismPropertyValue<T>> valuesToReplace) { clear(); addValues(valuesToReplace); } public boolean hasValue(PrismPropertyValue<T> value) { return super.hasValue(value); } public boolean hasRealValue(PrismPropertyValue<T> value) { for (PrismPropertyValue<T> propVal : getValues()) { if (propVal.equalsRealValue(value)) { return true; } } return false; } @Override public PrismPropertyValue<T> getPreviousValue(PrismValue value) { return (PrismPropertyValue<T>) super.getPreviousValue(value); } @Override public PrismPropertyValue<T> getNextValue(PrismValue value) { return (PrismPropertyValue<T>) super.getNextValue(value); } public Class<T> getValueClass() { if (getDefinition() != null) { return getDefinition().getTypeClass(); } if (!getValues().isEmpty()) { PrismPropertyValue<T> firstPVal = getValues().get(0); if (firstPVal != null) { T firstVal = firstPVal.getValue(); if (firstVal != null) { return (Class<T>) firstVal.getClass(); } } } // TODO: How to determine value class????? return PrismConstants.DEFAULT_VALUE_CLASS; } @Override public PropertyDelta<T> createDelta() { return new PropertyDelta<T>(getPath(), getDefinition(), prismContext); } @Override public PropertyDelta<T> createDelta(ItemPath path) { return new PropertyDelta<T>(path, getDefinition(), prismContext); } @Override public Object find(ItemPath path) { if (path == null || path.isEmpty()) { return this; } if (!isSingleValue()) { throw new IllegalStateException("Attempt to resolve sub-path '"+path+"' on multi-value property " + getElementName()); } PrismPropertyValue<T> value = getValue(); return value.find(path); } @Override public <IV extends PrismValue,ID extends ItemDefinition> PartiallyResolvedItem<IV,ID> findPartial(ItemPath path) { if (path == null || path.isEmpty()) { return new PartiallyResolvedItem<IV,ID>((Item<IV,ID>)this, null); } for (PrismPropertyValue<T> pvalue: getValues()) { T value = pvalue.getValue(); if (!(value instanceof Structured)) { throw new IllegalArgumentException("Attempt to resolve sub-path '"+path+"' on non-structured property value "+pvalue); } } return new PartiallyResolvedItem<IV,ID>((Item<IV,ID>)this, path); } public PropertyDelta<T> diff(PrismProperty<T> other) { return (PropertyDelta<T>) super.diff(other); } public PropertyDelta<T> diff(PrismProperty<T> other, boolean ignoreMetadata, boolean isLiteral) { return (PropertyDelta<T>) super.diff(other, true, false); } public static <T> PropertyDelta<T> diff(PrismProperty<T> a, PrismProperty<T> b) { if (a == null) { if (b == null) { return null; } PropertyDelta<T> delta = b.createDelta(); delta.addValuesToAdd(PrismValue.cloneCollection(b.getValues())); return delta; } else { return a.diff(b); } } @Override protected void checkDefinition(PrismPropertyDefinition<T> def) { if (def == null) { throw new IllegalArgumentException("Definition "+def+" cannot be applied to property "+this); } } @Override public PrismProperty<T> clone() { PrismProperty<T> clone = new PrismProperty<T>(getElementName(), getDefinition(), prismContext); copyValues(clone); return clone; } protected void copyValues(PrismProperty<T> clone) { super.copyValues(clone); for (PrismPropertyValue<T> value : getValues()) { clone.addValue(value.clone()); } } @Override public int hashCode() { int result = super.hashCode(); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; return true; } @Override protected ItemDelta fixupDelta(ItemDelta delta, Item otherItem, boolean ignoreMetadata) { PrismPropertyDefinition def = getDefinition(); if (def != null && def.isSingleValue() && !delta.isEmpty()) { // Drop the current delta (it was used only to detect that something has changed // Generate replace delta instead of add/delete delta PrismProperty<T> other = (PrismProperty<T>)otherItem; PropertyDelta<T> propertyDelta = (PropertyDelta<T>)delta; delta.clear(); Collection<PrismPropertyValue<T>> replaceValues = new ArrayList<PrismPropertyValue<T>>(other.getValues().size()); for (PrismPropertyValue<T> value : other.getValues()) { replaceValues.add(value.clone()); } propertyDelta.setValuesToReplace(replaceValues); return propertyDelta; } else { return super.fixupDelta(delta, otherItem, ignoreMetadata); } } public static boolean compareCollectionRealValues(Collection<? extends PrismProperty> col1, Collection<? extends PrismProperty> col2) { return MiscUtil.unorderedCollectionEquals(col1, col2, (p1, p2) -> { if (!p1.getElementName().equals(p2.getElementName())) { return false; } Collection p1RealVals = p1.getRealValues(); Collection p2RealVals = p2.getRealValues(); return MiscUtil.unorderedCollectionEquals(p1RealVals, p2RealVals); }); } @Override public String toString() { return getDebugDumpClassName() + "(" + PrettyPrinter.prettyPrint(getElementName()) + "):" + getValues(); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < indent; i++) { sb.append(INDENT_STRING); } if (DebugUtil.isDetailedDebugDump()) { sb.append(getDebugDumpClassName()).append(": "); } sb.append(DebugUtil.formatElementName(getElementName())).append(": "); boolean isMultivalue = true; PrismPropertyDefinition def = getDefinition(); if (def != null) { isMultivalue = def.isMultiValue(); } List<PrismPropertyValue<T>> values = getValues(); if (values.isEmpty()) { sb.append("[]"); } else { boolean multiline = false; PrismPropertyValue<T> firstVal = values.iterator().next(); if (firstVal != null && !firstVal.isRaw() && firstVal.getValue() != null) { if (DebugUtil.isDetailedDebugDump() && firstVal.getValue() instanceof DebugDumpable) { multiline = true; } else { if (PrettyPrinter.prettyPrint(firstVal.getValue()).length() > MAX_SINGLELINE_LEN) { multiline = true; } } } if (multiline) { sb.append("\n"); Iterator<PrismPropertyValue<T>> iterator = getValues().iterator(); while(iterator.hasNext()) { PrismPropertyValue<T> value = iterator.next(); if (value.isRaw()) { sb.append(formatRawValueForDump(value.getRawElement())); sb.append(" (raw)"); } else { T realValue = value.getValue(); if (realValue instanceof DebugDumpable) { sb.append(((DebugDumpable)realValue).debugDump(indent + 1)); } else { DebugUtil.indentDebugDump(sb, indent + 1); if (DebugUtil.isDetailedDebugDump()) { sb.append(PrettyPrinter.prettyPrint(value)); } else { PrismPropertyValue.debugDumpValue(sb, indent + 1, value.getValue(), prismContext); } } } if (iterator.hasNext()) { sb.append("\n"); } } } else { if (isMultivalue) { sb.append("[ "); } Iterator<PrismPropertyValue<T>> iterator = getValues().iterator(); while(iterator.hasNext()) { PrismPropertyValue<T> value = iterator.next(); if (value.isRaw()) { sb.append(formatRawValueForDump(value.getRawElement())); sb.append(" (raw)"); } else { if (DebugUtil.isDetailedDebugDump()) { sb.append(PrettyPrinter.prettyPrint(value)); } else { PrismPropertyValue.debugDumpValue(sb, indent + 1, value.getValue(), prismContext); } } if (iterator.hasNext()) { sb.append(", "); } } if (isMultivalue) { sb.append(" ]"); } } } appendDebugDumpSuffix(sb); if (def != null && DebugUtil.isDetailedDebugDump()) { sb.append(" def("); def.debugDumpShortToString(sb); // if (def.isIndexed() != null) { // sb.append(def.isIndexed() ? ",i+" : ",i-"); // } sb.append(")"); } return sb.toString(); } private String formatRawValueForDump(Object rawElement) { if (rawElement == null) { return null; } if (rawElement instanceof PrimitiveXNode<?>) { return ((PrimitiveXNode<?>)rawElement).getStringValue(); } else { return "<class " + rawElement.getClass().getSimpleName()+">"; } } public String toHumanReadableString() { StringBuilder sb = new StringBuilder(); sb.append(PrettyPrinter.prettyPrint(getElementName())).append(" = "); if (getValues() == null) { sb.append("null"); } else { sb.append("[ "); Iterator<PrismPropertyValue<T>> iterator = getValues().iterator(); while(iterator.hasNext()) { PrismPropertyValue<T> value = iterator.next(); sb.append(value.toHumanReadableString()); if (iterator.hasNext()) { sb.append(", "); } } sb.append(" ]"); } return sb.toString(); } /** * Return a human readable name of this class suitable for logs. */ @Override protected String getDebugDumpClassName() { return "PP"; } public static <T> PrismProperty<T> createRaw(@NotNull XNode node, @NotNull QName itemName, PrismContext prismContext) throws SchemaException { Validate.isTrue(!(node instanceof RootXNode)); PrismProperty<T> property = new PrismProperty<>(itemName, prismContext); if (node instanceof ListXNode) { for (XNode subnode : (ListXNode) node) { property.add(PrismPropertyValue.createRaw(subnode)); } } else { property.add(PrismPropertyValue.createRaw(node)); } return property; } }