com.evolveum.midpoint.prism.PrismPropertyValue.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.prism.PrismPropertyValue.java

Source

/*
 * 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.match.MatchingRule;
import com.evolveum.midpoint.prism.parser.XNodeProcessor;
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.schema.SchemaRegistry;
import com.evolveum.midpoint.prism.util.CloneUtil;
import com.evolveum.midpoint.prism.util.PrismUtil;
import com.evolveum.midpoint.prism.xnode.MapXNode;
import com.evolveum.midpoint.prism.xnode.PrimitiveXNode;
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.RawType;
import com.evolveum.prism.xml.ns._public.types_3.SchemaDefinitionType;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;

import org.apache.commons.lang.StringUtils;
import org.jvnet.jaxb2_commons.lang.Equals;
import org.w3c.dom.Element;

import javax.xml.bind.JAXBElement;
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 a dumb JAXB parsing or XML 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, 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) {
        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 && parent.getPrismContext() != null) {
                    def = SchemaRegistry.createDefaultItemDefinition(parent.getElementName(),
                            parent.getPrismContext());
                } else if (PrismContext.isAllowSchemalessSerialization()) {
                    if (rawElement instanceof Element) {
                        // Do the most stupid thing possible. Assume string value. And there will be no definition.
                        value = (T) ((Element) rawElement).getTextContent();
                    } else 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);
                }
            }
        }
        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 Object 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 {
        if (definition != null && rawElement != null) {
            value = (T) parseRawElementToNewRealValue(this, (PrismPropertyDefinition) definition);
            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 if (prismContext.getBeanConverter().canProcess(value.getClass())) {
                prismContext.getBeanConverter().revive(value, prismContext);
            }
        }
    }

    @Override
    public void recompute(PrismContext prismContext) {
        if (isRaw()) {
            return;
        }
        T realValue = getValue();
        if (realValue == null) {
            return;
        }
        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 + ")");
            }
            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 {
        XNodeProcessor xnodeProcessor = definition.getPrismContext().getXnodeProcessor();
        T value = xnodeProcessor.parsePrismPropertyRealValue(prismPropertyValue.rawElement, definition);
        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 wasIdent = false;
        if (DebugUtil.isDetailedDebugDump()) {
            DebugUtil.indentDebugDump(sb, indent);
            wasIdent = 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 (wasIdent) {
                    sb.append("\n");
                }
                sb.append(((DebugDumpable) value).debugDump(indent + 1));
            } else {
                if (!wasIdent) {
                    DebugUtil.indentDebugDump(sb, indent);
                    wasIdent = true;
                }
                sb.append(PrettyPrinter.prettyPrint(value));
            }
        } else {
            if (!wasIdent) {
                DebugUtil.indentDebugDump(sb, indent);
                wasIdent = true;
            }
            sb.append("null");
        }

        return sb.toString();
    }

    @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);
    }
}