/* * Copyright (c) 2010-2013 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.marshaller.BeanMarshaller; import com.evolveum.midpoint.prism.match.MatchingRule; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.prism.polystring.PolyStringNormalizer; import com.evolveum.midpoint.prism.util.CloneUtil; import com.evolveum.midpoint.prism.util.PrismUtil; import com.evolveum.midpoint.prism.xnode.XNode; import com.evolveum.midpoint.util.DOMUtil; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.PrettyPrinter; import com.evolveum.midpoint.util.exception.SchemaException; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType; import com.evolveum.prism.xml.ns._public.types_3.RawType; import com.evolveum.prism.xml.ns._public.types_3.SchemaDefinitionType; import java.io.Serializable; import java.util.*; import java.util.stream.Collectors; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jvnet.jaxb2_commons.lang.Equals; import org.w3c.dom.Element; import javax.xml.bind.JAXBElement; import javax.xml.bind.annotation.XmlType; import javax.xml.namespace.QName; /** * @author lazyman */ public class PrismPropertyValue<T> extends PrismValue implements DebugDumpable, Serializable { private T value; // The rawElement is set during a schema-less parsing, e.g. during parsing without a definition. // We can't do anything smarter, as we don't have definition nor prism context. So we store the raw // elements here and process them later (e.g. during applyDefinition or getting a value with explicit type). private XNode rawElement; public PrismPropertyValue(T value) { this(value, null, null); } public PrismPropertyValue(T value, PrismContext prismContext) { this(value, prismContext, null, null); } public PrismPropertyValue(T value, OriginType type, Objectable source) { this(value, null, type, source); } public PrismPropertyValue(T value, PrismContext prismContext, OriginType type, Objectable source) { super(type, source); if (value instanceof PrismPropertyValue) { throw new IllegalArgumentException("Probably problem somewhere, encapsulating property " + "value object to another property value."); } this.value = value; checkValue(); } /** * Private constructor just for clonning. */ private PrismPropertyValue(OriginType type, Objectable source) { super(type,source); } private PrismPropertyValue() { } public static <T> PrismPropertyValue<T> createRaw(XNode rawElement) { PrismPropertyValue<T> pval = new PrismPropertyValue<T>(); pval.setRawElement(rawElement); return pval; } public void setValue(T value) { checkMutability(); this.value = value; checkValue(); } public T getValue() { if (rawElement != null) { ItemDefinition def = null; Itemable parent = getParent(); if (parent != null && parent.getDefinition() != null) { def = getParent().getDefinition(); } // if (def == null) { // // We are weak now. If there is no better definition for this we assume a default definition and process // // the attribute now. But we should rather do this: TODO: // // throw new IllegalStateException("Attempt to get value withot a type from raw value of property "+getParent()); // if (parent != null && getPrismContext() != null) { // def = SchemaRegistryImpl.createDefaultItemDefinition(parent.getElementName(), getPrismContext()); // } else if (PrismContextImpl.isAllowSchemalessSerialization()) { // if (rawElement instanceof PrimitiveXNode) { // try { // QName type = rawElement.getTypeQName() != null ? rawElement.getTypeQName() : DOMUtil.XSD_STRING; // value = (T) ((PrimitiveXNode) rawElement).getParsedValueWithoutRecording(type); // } catch (SchemaException ex){ // throw new IllegalStateException("Cannot fetch value from raw element. " + ex.getMessage(), ex); // } // } else { // throw new IllegalStateException("No parent or prism context in property value "+this+", cannot create default definition." + // "The element is also not a DOM element but it is "+rawElement.getClass()+". Epic fail."); // } // } else { // throw new IllegalStateException("No parent or prism context in property value "+this+" (schemaless serialization is disabled)"); // } // } if (def != null) { try { applyDefinition(def); } catch (SchemaException e) { throw new IllegalStateException(e.getMessage(),e); } } if (rawElement != null) { return (T) RawType.create(rawElement, getPrismContext()); } } return value; } public static <T> Collection<T> getValues(Collection<PrismPropertyValue<T>> pvals) { Collection<T> realValues = new ArrayList<T>(pvals.size()); for (PrismPropertyValue<T> pval: pvals) { realValues.add(pval.getValue()); } return realValues; } public XNode getRawElement() { return rawElement; } public void setRawElement(XNode rawElement) { this.rawElement = rawElement; } @Override public boolean isRaw() { return rawElement != null; } @Override public void applyDefinition(ItemDefinition definition) throws SchemaException { PrismPropertyDefinition propertyDefinition = (PrismPropertyDefinition) definition; if (propertyDefinition != null && !propertyDefinition.isAnyType() && rawElement != null) { value = (T) parseRawElementToNewRealValue(this, propertyDefinition); rawElement = null; } } @Override public void applyDefinition(ItemDefinition definition, boolean force) throws SchemaException { applyDefinition(definition); } @Override public void revive(PrismContext prismContext) throws SchemaException { super.revive(prismContext); if (value != null) { if (value instanceof Revivable) { ((Revivable)value).revive(prismContext); } else { BeanMarshaller marshaller = ((PrismContextImpl) prismContext).getBeanMarshaller(); if (marshaller.canProcess(value.getClass())) { marshaller.revive(value, prismContext); } } } } @Override public void recompute(PrismContext prismContext) { if (isRaw()) { return; } T realValue = getValue(); if (realValue == null) { return; } checkMutability(); // TODO reconsider this PrismUtil.recomputeRealValue(realValue, prismContext); } @Override public Object find(ItemPath path) { if (path == null || path.isEmpty()) { return this; } T value = getValue(); if (value instanceof Structured) { return ((Structured)value).resolve(path); } else { throw new IllegalArgumentException("Attempt to resolve sub-path '"+path+"' on non-structured property value "+value); } } @Override public <IV extends PrismValue,ID extends ItemDefinition> PartiallyResolvedItem<IV,ID> findPartial(ItemPath path) { throw new UnsupportedOperationException("Attempt to invoke findPartialItem on a property value"); } void checkValue() { if (isRaw()) { // Cannot really check raw values return; } if (value == null) { // can be used not because of prism forms in gui (will be fixed later [lazyman] // throw new IllegalArgumentException("Null value in "+this); return; } if (value instanceof PolyStringType) { // This is illegal. PolyString should be there instead. throw new IllegalArgumentException("PolyStringType found where PolyString should be in "+this); } Class<? extends Object> valueClass = value.getClass(); if (value instanceof Serializable) { // This is OK return; } if (valueClass.isPrimitive()) { // This is OK return; } if (value instanceof SchemaDefinitionType) { return; } if (value instanceof RawType) { return; } throw new IllegalArgumentException("Unsupported value "+value+" ("+valueClass+") in "+this); } @Override public void checkConsistenceInternal(Itemable rootItem, boolean requireDefinitions, boolean prohibitRaw, ConsistencyCheckScope scope) { if (!scope.isThorough()) { return; } ItemPath myPath = getPath(); if (prohibitRaw && rawElement != null) { throw new IllegalStateException("Raw element in property value "+this+" ("+myPath+" in "+rootItem+")"); } if (value == null && rawElement == null) { throw new IllegalStateException("Neither value nor raw element specified in property value "+this+" ("+myPath+" in "+rootItem+")"); } if (value != null && rawElement != null) { throw new IllegalStateException("Both value and raw element specified in property value "+this+" ("+myPath+" in "+rootItem+")"); } if (value != null) { if (value instanceof Recomputable) { try { ((Recomputable)value).checkConsistence(); } catch (IllegalStateException e) { throw new IllegalStateException(e.getMessage()+" in property value "+this+" ("+myPath+" in "+rootItem+")", e); } } if (value instanceof PolyStringType) { throw new IllegalStateException("PolyStringType found in property value "+this+" ("+myPath+" in "+rootItem+")"); } if (value instanceof ProtectedStringType) { if (((ProtectedStringType)value).isEmpty()) { throw new IllegalStateException("Empty ProtectedStringType found in property value "+this+" ("+myPath+" in "+rootItem+")"); } } PrismContext prismContext = getPrismContext(); if (value instanceof PolyString && prismContext != null) { PolyString poly = (PolyString)value; String orig = poly.getOrig(); String norm = poly.getNorm(); PolyStringNormalizer polyStringNormalizer = prismContext.getDefaultPolyStringNormalizer(); String expectedNorm = polyStringNormalizer.normalize(orig); if (!norm.equals(expectedNorm)) { throw new IllegalStateException("PolyString has inconsistent orig ("+orig+") and norm ("+norm+") in property value "+this+" ("+myPath+" in "+rootItem+")"); } } } } @Override public boolean isEmpty() { return value == null; } @Override public PrismPropertyValue<T> clone() { PrismPropertyValue clone = new PrismPropertyValue(getOriginType(), getOriginObject()); copyValues(clone); return clone; } protected void copyValues(PrismPropertyValue clone) { super.copyValues(clone); clone.value = CloneUtil.clone(this.value); clone.rawElement = this.rawElement; } public static boolean containsRealValue(Collection<PrismPropertyValue<?>> collection, PrismPropertyValue<?> value) { for (PrismPropertyValue<?> colVal: collection) { if (value.equalsRealValue(colVal)) { return true; } } return false; } public static boolean containsValue(Collection<PrismPropertyValue> collection, PrismPropertyValue value, Comparator comparator) { for (PrismPropertyValue<?> colVal: collection) { if (comparator.compare(colVal, value) == 0) { return true; } } return false; } public static <T> Collection<PrismPropertyValue<T>> createCollection(Collection<T> realValueCollection) { Collection<PrismPropertyValue<T>> pvalCol = new ArrayList<PrismPropertyValue<T>>(realValueCollection.size()); for (T realValue: realValueCollection) { PrismPropertyValue<T> pval = new PrismPropertyValue<T>(realValue); pvalCol.add(pval); } return pvalCol; } public static <T> Collection<PrismPropertyValue<T>> createCollection(T[] realValueArray) { Collection<PrismPropertyValue<T>> pvalCol = new ArrayList<PrismPropertyValue<T>>(realValueArray.length); for (T realValue: realValueArray) { PrismPropertyValue<T> pval = new PrismPropertyValue<T>(realValue); pvalCol.add(pval); } return pvalCol; } /** * Takes the definition from the definitionSource parameter and uses it to parse raw elements in origValue. * It returns a new parsed value without touching the original value. */ private PrismPropertyValue<T> parseRawElementToNewValue(PrismPropertyValue<T> origValue, PrismPropertyValue<T> definitionSource) throws SchemaException { if (definitionSource.getParent() != null && definitionSource.getParent().getDefinition() != null) { T parsedRealValue = (T) parseRawElementToNewRealValue(origValue, (PrismPropertyDefinition) definitionSource.getParent().getDefinition()); PrismPropertyValue<T> newPVal = new PrismPropertyValue<T>(parsedRealValue); return newPVal; } else { throw new IllegalArgumentException("Attempt to use property " + origValue.getParent() + " values in a raw parsing state (raw elements) with parsed value that has no definition"); } } private T parseRawElementToNewRealValue(PrismPropertyValue<T> prismPropertyValue, PrismPropertyDefinition<T> definition) throws SchemaException { PrismContext prismContext = definition.getPrismContext(); //noinspection UnnecessaryLocalVariable T value = prismContext.parserFor(prismPropertyValue.rawElement.toRootXNode()).definition(definition).parseRealValue(); return value; } @Override public boolean equalsComplex(PrismValue other, boolean ignoreMetadata, boolean isLiteral) { if (other == null || !(other instanceof PrismPropertyValue)) { return false; } return equalsComplex((PrismPropertyValue<?>)other, ignoreMetadata, isLiteral, null); } public boolean equalsComplex(PrismPropertyValue<?> other, boolean ignoreMetadata, boolean isLiteral, MatchingRule<T> matchingRule) { if (!super.equalsComplex(other, ignoreMetadata, isLiteral)) { return false; } if (this.rawElement != null && other.rawElement != null) { return equalsRawElements((PrismPropertyValue<T>)other); } PrismPropertyValue<T> otherProcessed = (PrismPropertyValue<T>) other; PrismPropertyValue<T> thisProcessed = this; if (this.rawElement != null || other.rawElement != null) { try { if (this.rawElement == null) { otherProcessed = parseRawElementToNewValue((PrismPropertyValue<T>) other, this); } else if (other.rawElement == null) { thisProcessed = parseRawElementToNewValue(this, (PrismPropertyValue<T>) other); } } catch (SchemaException e) { // TODO: Maybe just return false? throw new IllegalArgumentException("Error parsing the value of property "+getParent()+" using the 'other' definition "+ "during a compare: "+e.getMessage(),e); } } T otherRealValue = otherProcessed.getValue(); T thisRealValue = thisProcessed.getValue(); if (otherRealValue == null && thisRealValue == null) { return true; } if (otherRealValue == null || thisRealValue == null) { return false; } if (matchingRule != null) { try { return matchingRule.match(thisRealValue, otherRealValue); } catch (SchemaException e) { // At least one of the values is invalid. But we do not want to throw exception from // a comparison operation. That will make the system very fragile. Let's fall back to // ordinary equality mechanism instead. return thisRealValue.equals(otherRealValue); } } else { if (thisRealValue instanceof Element && otherRealValue instanceof Element) { return DOMUtil.compareElement((Element)thisRealValue, (Element)otherRealValue, isLiteral); } if (thisRealValue instanceof SchemaDefinitionType && otherRealValue instanceof SchemaDefinitionType) { SchemaDefinitionType thisSchema = (SchemaDefinitionType) thisRealValue; return thisSchema.equals(otherRealValue, isLiteral); // return DOMUtil.compareElement((Element)thisRealValue, (Element)otherRealValue, isLiteral); } if (thisRealValue instanceof byte[] && otherRealValue instanceof byte[]) { return Arrays.equals((byte[]) thisRealValue, (byte[]) otherRealValue); } if (isLiteral) { if (thisRealValue instanceof QName && otherRealValue instanceof QName) { // we compare prefixes as well if (!thisRealValue.equals(otherRealValue)) { return false; } return StringUtils.equals(((QName) thisRealValue).getPrefix(), ((QName) otherRealValue).getPrefix()); } else if (thisRealValue instanceof Equals && otherRealValue instanceof Equals) { return ((Equals) thisRealValue).equals(null, null, otherRealValue, LiteralEqualsStrategy.INSTANCE); } } return thisRealValue.equals(otherRealValue); } } private boolean equalsRawElements(PrismPropertyValue<T> other) { return this.rawElement.equals(other.rawElement); } @Override public boolean match(PrismValue otherValue) { if (otherValue == null || !(otherValue instanceof PrismPropertyValue)) { return false; } return matchComplex((PrismPropertyValue<?>)otherValue, false, false); } private boolean matchComplex(PrismPropertyValue<?> otherValue, boolean ignoreMetadata, boolean isLiteral) { if (!super.equalsComplex(otherValue, ignoreMetadata, isLiteral)) { return false; } if (this.rawElement != null && otherValue.rawElement != null) { return equalsRawElements((PrismPropertyValue<T>)otherValue); } PrismPropertyValue<T> otherProcessed = (PrismPropertyValue<T>) otherValue; PrismPropertyValue<T> thisProcessed = this; if (this.rawElement != null || otherValue.rawElement != null) { try { if (this.rawElement == null) { otherProcessed = parseRawElementToNewValue((PrismPropertyValue<T>) otherValue, this); } else if (otherValue.rawElement == null) { thisProcessed = parseRawElementToNewValue(this, (PrismPropertyValue<T>) otherValue); } } catch (SchemaException e) { // TODO: Maybe just return false? throw new IllegalArgumentException("Error parsing the value of property "+getParent()+" using the 'other' definition "+ "during a compare: "+e.getMessage(),e); } } T otherRealValue = otherProcessed.getValue(); T thisRealValue = thisProcessed.getValue(); if (otherRealValue == null && thisRealValue == null) { return true; } if (otherRealValue == null || thisRealValue == null) { return false; } if (thisRealValue instanceof Element && otherRealValue instanceof Element) { return DOMUtil.compareElement((Element)thisRealValue, (Element)otherRealValue, isLiteral); } if (otherRealValue instanceof Matchable && thisRealValue instanceof Matchable){ Matchable thisMatchableValue = (Matchable) thisRealValue; Matchable otherMatchableValue = (Matchable) otherRealValue; return thisMatchableValue.match(otherMatchableValue); } return thisRealValue.equals(otherRealValue); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!super.equals(obj)) return false; if (getClass() != obj.getClass()) return false; PrismPropertyValue other = (PrismPropertyValue) obj; return equalsComplex(other, false, false, null); } @Override public int hashCode() { final int prime = 31; int result = super.hashCode(); if (value != null && value instanceof Element) { // We need special handling here. We haven't found out the proper way now. // so we just do not include this in the hashcode now. } else { result = prime * result + ((value == null) ? 0 : value.hashCode()); } return result; } @Override public String debugDump() { return toString(); } @Override public String debugDump(int indent) { StringBuilder sb = new StringBuilder(); boolean wasIndent = false; if (DebugUtil.isDetailedDebugDump()) { DebugUtil.indentDebugDump(sb, indent); wasIndent = true; sb.append("PPV("); dumpSuffix(sb); sb.append("):"); } if (value != null) { if (DebugUtil.isDetailedDebugDump()) { sb.append(" ").append(value.getClass().getSimpleName()).append(":"); } if (value instanceof DebugDumpable) { if (wasIndent) { sb.append("\n"); } sb.append(((DebugDumpable)value).debugDump(indent + 1)); } else { if (!wasIndent) { DebugUtil.indentDebugDump(sb, indent); } debugDumpValue(sb, indent, value, prismContext); } } else { if (!wasIndent) { DebugUtil.indentDebugDump(sb, indent); } sb.append("null"); } return sb.toString(); } public static void debugDumpValue(StringBuilder sb, int indent, Object value, PrismContext prismContext) { String formatted; if (DebugUtil.getPrettyPrintBeansAs() != null && value != null && !(value instanceof Enum) && prismContext != null && value.getClass().getAnnotation(XmlType.class) != null) { try { formatted = prismContext.serializerFor(DebugUtil.getPrettyPrintBeansAs()).serializeRealValue(value, new QName("value")); } catch (SchemaException e) { formatted = PrettyPrinter.prettyPrint(value); } } else { formatted = PrettyPrinter.prettyPrint(value); } sb.append(DebugUtil.fixIndentInMultiline(indent, DebugDumpable.INDENT_STRING, formatted)); } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("PPV("); // getValue() must not be here. getValue() contains exception that in turn causes a call to toString() if (value != null) { builder.append(value.getClass().getSimpleName()).append(":"); builder.append(PrettyPrinter.prettyPrint(value)); } else { builder.append("null"); } dumpSuffix(builder); builder.append(")"); return builder.toString(); } private void dumpSuffix(StringBuilder builder) { appendOriginDump(builder); if (getRawElement() != null) { builder.append(", raw element: "); builder.append(PrettyPrinter.prettyPrint(getRawElement())); } } @Override public String toHumanReadableString() { return PrettyPrinter.prettyPrint(value); } /** * Returns JAXBElement corresponding to the this value. * Name of the element is the name of parent property; its value is the real value of the property. * * @return Created JAXBElement. */ public JAXBElement<T> toJaxbElement() { Itemable parent = getParent(); if (parent == null) { throw new IllegalStateException("Couldn't represent parent-less property value as a JAXBElement"); } Object realValue = getValue(); return new JAXBElement<T>(parent.getElementName(), (Class) realValue.getClass(), (T) realValue); } @Override public Class<?> getRealClass() { return value != null ? value.getClass() : null; } @Nullable @Override public <T> T getRealValue() { return (T) getValue(); } public static <T> Collection<PrismPropertyValue<T>> wrap(@NotNull Collection<T> realValues) { return realValues.stream() .map(val -> new PrismPropertyValue<>(val)) .collect(Collectors.toList()); } }