com.evolveum.midpoint.prism.parser.XNodeProcessor.java Source code

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2014-2015 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.parser;

import java.util.Collection;
import java.util.Map.Entry;

import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;

import com.evolveum.midpoint.prism.PrismConstants;
import com.evolveum.midpoint.prism.SerializationContext;
import com.evolveum.midpoint.util.JAXBUtil;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.prism.xml.ns._public.query_3.SearchFilterType;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;

import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerDefinition;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismObjectDefinition;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyDefinition;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismReference;
import com.evolveum.midpoint.prism.PrismReferenceDefinition;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.schema.PrismSchema;
import com.evolveum.midpoint.prism.schema.SchemaRegistry;
import com.evolveum.midpoint.prism.util.PrismUtil;
import com.evolveum.midpoint.prism.xnode.ListXNode;
import com.evolveum.midpoint.prism.xnode.MapXNode;
import com.evolveum.midpoint.prism.xnode.PrimitiveXNode;
import com.evolveum.midpoint.prism.xnode.RootXNode;
import com.evolveum.midpoint.prism.xnode.SchemaXNode;
import com.evolveum.midpoint.prism.xnode.XNode;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.DisplayableValue;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.prism.xml.ns._public.types_3.ItemPathType;
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.SchemaDefinitionType;

public class XNodeProcessor {

    private static final Trace LOGGER = TraceManager.getTrace(XNodeProcessor.class);

    public static final String ARTIFICIAL_OBJECT_NAME = "anObject";

    private PrismContext prismContext;
    private XNodeProcessorEvaluationMode mode = XNodeProcessorEvaluationMode.STRICT;

    public XNodeProcessor() {
    }

    public XNodeProcessor(PrismContext prismContext) {
        this.prismContext = prismContext;
    }

    public XNodeProcessor(PrismContext prismContext, XNodeProcessorEvaluationMode mode) {
        this.prismContext = prismContext;
        this.mode = mode;
    }

    public SchemaRegistry getSchemaRegistry() {
        return prismContext.getSchemaRegistry();
    }

    public PrismContext getPrismContext() {
        return prismContext;
    }

    public void setPrismContext(PrismContext prismContext) {
        this.prismContext = prismContext;
    }

    public XNodeProcessorEvaluationMode getMode() {
        return mode;
    }

    public void setMode(XNodeProcessorEvaluationMode mode) {
        this.mode = mode;
    }

    private boolean isStrict() {
        return mode == XNodeProcessorEvaluationMode.STRICT;
    }

    //region Parsing prism objects
    public <O extends Objectable> PrismObject<O> parseObject(XNode xnode) throws SchemaException {
        if (xnode instanceof RootXNode) {
            return parseObject((RootXNode) xnode);
        } else if (xnode instanceof MapXNode) {
            return parseObject((MapXNode) xnode);
        } else {
            throw new IllegalArgumentException("Cannot parse object from " + xnode);
        }
    }

    public <O extends Objectable> PrismObject<O> parseObject(RootXNode rootXnode) throws SchemaException {
        QName rootElementName = rootXnode.getRootElementName();
        PrismObjectDefinition<O> objectDefinition = null;
        if (rootXnode.getTypeQName() != null) {
            objectDefinition = getSchemaRegistry().findObjectDefinitionByType(rootXnode.getTypeQName());
            if (objectDefinition == null) {
                throw new SchemaException("No object definition for type " + rootXnode.getTypeQName());
            }
        } else {
            objectDefinition = getSchemaRegistry().findObjectDefinitionByElementName(rootElementName);
            if (objectDefinition == null) {
                throw new SchemaException("No object definition for element name " + rootElementName);
            }
        }
        if (objectDefinition == null) {
            throw new SchemaException("Cannot locate object definition (unspecified reason)");
        }
        XNode subnode = rootXnode.getSubnode();
        if (!(subnode instanceof MapXNode)) {
            throw new IllegalArgumentException(
                    "Cannot parse object from " + subnode.getClass().getSimpleName() + ", we need a map");
        }
        return parseObject((MapXNode) subnode, rootElementName, objectDefinition);
    }

    public <O extends Objectable> PrismObject<O> parseObject(MapXNode xmap) throws SchemaException {
        // There is no top-level element to detect type. We have only one chance ...
        QName typeQName = xmap.getTypeQName();
        if (typeQName == null) {
            throw new SchemaException("No type specified in top-level xnode, cannot determine object type");
        }
        PrismObjectDefinition<O> objectDefinition = getSchemaRegistry().findObjectDefinitionByType(typeQName);
        return parseObject(xmap, objectDefinition);
    }

    private <O extends Objectable> PrismObject<O> parseObject(MapXNode xnode,
            PrismObjectDefinition<O> objectDefinition) throws SchemaException {
        QName elementName;
        if (objectDefinition != null) {
            elementName = objectDefinition.getName();
        } else {
            elementName = new QName(null, ARTIFICIAL_OBJECT_NAME);
        }
        return parseObject(xnode, elementName, objectDefinition);
    }

    private <O extends Objectable> PrismObject<O> parseObject(XNode xnode, QName elementName,
            PrismObjectDefinition<O> objectDefinition) throws SchemaException {
        if (xnode instanceof RootXNode) {
            return parseObject(((RootXNode) xnode).getSubnode(), elementName, objectDefinition); // elementName from RootXNode is silently ignored
        } else if (xnode instanceof MapXNode) {
            return parseObject((MapXNode) xnode, elementName, objectDefinition);
        } else {
            throw new IllegalArgumentException("Cannot parse object from " + xnode);
        }
    }

    private <O extends Objectable> PrismObject<O> parseObject(MapXNode xnode, QName elementName,
            PrismObjectDefinition<O> objectDefinition) throws SchemaException {
        PrismObject<O> object = (PrismObject<O>) parseContainerFromMapInternal(xnode, elementName, objectDefinition,
                QNameUtil.createCollection(XNode.KEY_OID, XNode.KEY_VERSION));
        object.setOid(getOid(xnode));
        object.setVersion(getVersion(xnode));
        return object;
    }

    private String getOid(MapXNode xmap) throws SchemaException {
        return xmap.getParsedPrimitiveValue(XNode.KEY_OID, DOMUtil.XSD_STRING);
    }

    private String getVersion(MapXNode xmap) throws SchemaException {
        return xmap.getParsedPrimitiveValue(XNode.KEY_VERSION, DOMUtil.XSD_STRING);
    }
    //endregion

    //region Parsing prism containers
    public <C extends Containerable> PrismContainer<C> parseContainer(XNode xnode) throws SchemaException {
        Validate.notNull(xnode);

        if (xnode instanceof RootXNode) {
            return parseContainer((RootXNode) xnode);
        } else if (xnode.getTypeQName() != null) {
            PrismContainerDefinition<C> definition = getSchemaRegistry()
                    .findContainerDefinitionByType(xnode.getTypeQName());
            if (definition == null) {
                throw new SchemaException("No container definition for type " + xnode.getTypeQName());
            }
            return parseContainer(xnode, definition);
        } else {
            throw new SchemaException("Couldn't parse container because no element name nor type name is known");
        }
    }

    public <C extends Containerable> PrismContainer<C> parseContainer(RootXNode rootXnode) throws SchemaException {
        Validate.notNull(rootXnode);

        QName rootElementName = rootXnode.getRootElementName();
        PrismContainerDefinition<C> definition;
        if (rootXnode.getTypeQName() != null) {
            definition = getSchemaRegistry().findContainerDefinitionByType(rootXnode.getTypeQName());
            if (definition == null) {
                throw new SchemaException("No container definition for type " + rootXnode.getTypeQName());
            }
        } else {
            definition = getSchemaRegistry().findContainerDefinitionByElementName(rootElementName);
            if (definition == null) {
                throw new SchemaException("No container definition for element name " + rootElementName);
            }
        }
        XNode subnode = rootXnode.getSubnode();
        if (!(subnode instanceof MapXNode)) {
            throw new IllegalArgumentException(
                    "Cannot parse object from " + subnode.getClass().getSimpleName() + ", we need a map");
        }
        return parseContainerInternal(subnode, rootElementName, definition);
    }

    public <C extends Containerable> PrismContainer<C> parseContainer(XNode xnode, Class<C> type)
            throws SchemaException {
        Validate.notNull(xnode);
        Validate.notNull(type);
        PrismContainerDefinition<C> definition = getSchemaRegistry()
                .findContainerDefinitionByCompileTimeClass(type);
        if (definition == null) {
            throw new SchemaException("No container definition for class " + type);
        }
        return parseContainer(xnode, definition);
    }

    public <C extends Containerable> PrismContainer<C> parseContainer(XNode xnode,
            PrismContainerDefinition<C> definition) throws SchemaException {
        Validate.notNull(xnode);
        Validate.notNull(definition);
        if (xnode instanceof RootXNode) {
            RootXNode xroot = (RootXNode) xnode;
            return parseContainerInternal(xroot.getSubnode(), xroot.getRootElementName(), definition);
        } else if (xnode instanceof MapXNode) {
            return parseContainerInternal(xnode, definition.getName(), definition);
        } else {
            throw new SchemaException("Cannot parse container from " + xnode);
        }
    }

    private <C extends Containerable> PrismContainer<C> parseContainerInternal(XNode xnode, QName elementName,
            PrismContainerDefinition<C> containerDef) throws SchemaException {
        if (xnode instanceof RootXNode) {
            RootXNode rootXnode = (RootXNode) xnode;
            return parseContainerInternal(rootXnode.getSubnode(), rootXnode.getRootElementName(), containerDef); // ignoring elementName from parameters (probably set to be root element name)
        } else if (xnode instanceof MapXNode) {
            return parseContainerFromMapInternal((MapXNode) xnode, elementName, containerDef, null);
        } else if (xnode instanceof ListXNode) {
            PrismContainer<C> container = containerDef.instantiate(elementName);
            for (XNode xsubnode : (ListXNode) xnode) {
                PrismContainerValue<C> containerValue = parsePrismContainerValue(xsubnode, containerDef);
                container.add(containerValue);
            }
            return container;
        } else if (xnode instanceof PrimitiveXNode<?>) {
            PrimitiveXNode<?> xprim = (PrimitiveXNode<?>) xnode;
            if (xprim.isEmpty()) {
                PrismContainer<C> container = containerDef.instantiate(elementName);
                container.createNewValue(); // empty primitive node corresponds actually to empty container VALUE, not empty (value-less) container ITEM
                return container;
            } else {
                throw new IllegalArgumentException("Cannot parse container from (non-empty) " + xnode);
            }
        } else {
            throw new IllegalArgumentException("Cannot parse container from " + xnode);
        }
    }

    private <C extends Containerable> PrismContainer<C> parseContainerFromMapInternal(MapXNode xmap,
            QName elementName, PrismContainerDefinition<C> containerDef, Collection<QName> ignoredItems)
            throws SchemaException {
        PrismContainer<C> container = containerDef.instantiate(elementName);
        PrismContainerValue<C> cval = parsePrismContainerValueFromMap(xmap, containerDef, ignoredItems);
        container.add(cval);
        return container;
    }

    public <C extends Containerable> PrismContainerValue<C> parsePrismContainerValue(XNode xnode,
            PrismContainerDefinition<C> containerDef) throws SchemaException {
        if (xnode instanceof MapXNode) {
            return parsePrismContainerValueFromMap((MapXNode) xnode, containerDef, null);
        } else if (xnode instanceof PrimitiveXNode) {
            PrimitiveXNode<?> xprim = (PrimitiveXNode<?>) xnode;
            if (xprim.isEmpty()) {
                PrismContainer<C> container = containerDef.instantiate();
                PrismContainerValue pcv = container.createNewValue();
                pcv.setParent(null);
                return pcv;
            } else {
                throw new IllegalArgumentException("Cannot parse container value from (non-empty) " + xnode);
            }
        } else {
            throw new IllegalArgumentException("Cannot parse container value from " + xnode);
        }
    }

    private <C extends Containerable> PrismContainerValue<C> parsePrismContainerValueFromMap(MapXNode xmap,
            PrismContainerDefinition<C> containerDef, Collection<QName> ignoredItems) throws SchemaException {
        Long id = getContainerId(xmap);

        // override container definition, if explicit type is specified
        PrismContainerDefinition valueDefinition = containerDef;
        if (xmap.getTypeQName() != null) {
            PrismContainerDefinition specificDef = prismContext.getSchemaRegistry()
                    .findContainerDefinitionByType(xmap.getTypeQName());
            if (specificDef != null) {
                valueDefinition = specificDef;
            } else {
                // TODO raise exception here?
                // by silently proceeding we risk losing some subclass-specific items
            }
        }
        PrismContainerValue<C> cval = new PrismContainerValue<C>(null, null, null, id, xmap.getTypeQName(),
                prismContext);
        for (Entry<QName, XNode> xentry : xmap.entrySet()) {
            QName itemQName = xentry.getKey();
            if (QNameUtil.match(itemQName, XNode.KEY_CONTAINER_ID)) {
                continue;
            }
            if (QNameUtil.matchAny(itemQName, ignoredItems)) {
                continue;
            }
            ItemDefinition itemDef = locateItemDefinition(valueDefinition, itemQName, xentry.getValue());
            if (itemDef == null) {
                if (valueDefinition.isRuntimeSchema()) {
                    PrismSchema itemSchema = getSchemaRegistry().findSchemaByNamespace(itemQName.getNamespaceURI());
                    if (itemSchema != null) {
                        // If we already have schema for this namespace then a missing element is
                        // an error. We positively know that it is not in the schema.
                        if (isStrict()) {
                            throw new SchemaException(
                                    "Item " + itemQName + " has no definition (schema present, in container "
                                            + containerDef + ")" + "while parsing " + xmap.debugDump(),
                                    itemQName);
                        } else {
                            // Just skip item
                            continue;
                        }
                    } else {
                        // No definition for item, but the schema is runtime. the definition may come later.
                        // Null is OK here. The item will be parsed as "raw"
                    }
                } else {
                    if (isStrict()) {
                        throw new SchemaException("Item " + itemQName + " has no definition (in container value "
                                + valueDefinition + ")" + "while parsing " + xmap.debugDump(), itemQName);
                    } else {
                        // Just skip item
                        continue;
                    }
                }
            }
            Item<?, ?> item = parseItem(xentry.getValue(), itemQName, itemDef);
            // Merge must be here, not just add. Some items (e.g. references) have alternative
            // names and representations and these cannot be processed as one map or list
            if (item != null) {
                cval.merge(item);
            }
        }
        return cval;
    }

    private Long getContainerId(MapXNode xmap) throws SchemaException {
        return xmap.getParsedPrimitiveValue(XNode.KEY_CONTAINER_ID, DOMUtil.XSD_LONG);
    }
    //endregion

    //region Parsing prism properties
    private <T> PrismProperty<T> parsePrismProperty(XNode xnode, QName propName,
            PrismPropertyDefinition<T> propertyDefinition) throws SchemaException {
        Validate.notNull(propertyDefinition);
        if (xnode == null) {
            return propertyDefinition.instantiate();
        } else if (xnode instanceof ListXNode) {
            return parsePrismPropertyFromList((ListXNode) xnode, propName, propertyDefinition);
        } else if (xnode instanceof MapXNode) {
            return parsePrismPropertyFromMap((MapXNode) xnode, propName, propertyDefinition);
        } else if (xnode instanceof PrimitiveXNode<?>) {
            return parsePrismPropertyFromPrimitive((PrimitiveXNode) xnode, propName, propertyDefinition);
        } else if (xnode instanceof SchemaXNode) {
            return parsePrismPropertyFromSchema((SchemaXNode) xnode, propName, propertyDefinition);
        } else if (xnode instanceof RootXNode) {
            return parsePrismProperty(((RootXNode) xnode).getSubnode(), propName, propertyDefinition); // todo what about possibly lost type name of root node?
        } else {
            throw new IllegalArgumentException("Cannot parse property from " + xnode);
        }
    }

    private <T> PrismProperty<T> parsePrismPropertyFromSchema(SchemaXNode xnode, QName propName,
            PrismPropertyDefinition<T> propertyDefinition) throws SchemaException {
        PrismProperty prop = propertyDefinition.instantiate();

        SchemaDefinitionType schemaDefType = getBeanConverter().unmarshalSchemaDefinitionType((SchemaXNode) xnode);
        PrismPropertyValue<SchemaDefinitionType> val = new PrismPropertyValue<>(schemaDefType);
        prop.add(val);

        return prop;
    }

    private <T> PrismProperty<T> parsePrismPropertyFromList(ListXNode xlist, QName propName,
            PrismPropertyDefinition<T> propertyDefinition) throws SchemaException {
        if (xlist == null || xlist.isEmpty()) {
            return null;
        }
        PrismProperty<T> prop = propertyDefinition.instantiate(propName);

        if (!propertyDefinition.isMultiValue() && xlist.size() > 1) {
            throw new SchemaException("Attempt to store multiple values in single-valued property " + propName);
        }

        for (XNode xsubnode : xlist) {
            PrismPropertyValue<T> pval = parsePrismPropertyValue(xsubnode, prop);
            if (pval != null) {
                prop.add(pval);
            }
        }
        return prop;
    }

    private <T> PrismProperty<T> parsePrismPropertyFromMap(MapXNode xmap, QName propName,
            PrismPropertyDefinition<T> propertyDefinition) throws SchemaException {
        PrismProperty<T> prop = propertyDefinition.instantiate(propName);
        PrismPropertyValue<T> pval = parsePrismPropertyValue(xmap, prop);
        if (pval != null) {
            prop.add(pval);
        }
        return prop;
    }

    private <T> PrismProperty<T> parsePrismPropertyFromPrimitive(PrimitiveXNode<T> xprim, QName propName,
            PrismPropertyDefinition<T> propertyDefinition) throws SchemaException {
        PrismProperty<T> prop = propertyDefinition.instantiate(propName);
        PrismPropertyValue<T> pval = parsePrismPropertyValue(xprim, prop);
        if (pval != null) {
            prop.add(pval);
        }
        return prop;
    }

    private <T> PrismPropertyValue<T> parsePrismPropertyValue(XNode xnode, PrismProperty<T> property)
            throws SchemaException {
        Validate.notNull(xnode);
        Validate.notNull(property);
        T realValue = parsePrismPropertyRealValue(xnode, property.getDefinition());
        if (realValue == null) {
            return null;
        }
        return new PrismPropertyValue<T>(realValue);
    }

    public <T> T parsePrismPropertyRealValue(XNode xnode, PrismPropertyDefinition<T> propertyDef)
            throws SchemaException {
        if (xnode instanceof PrimitiveXNode<?>) {
            return parseAtomicValueFromPrimitive((PrimitiveXNode<T>) xnode, propertyDef, propertyDef.getTypeName());
        } else if (xnode instanceof MapXNode) {
            return parsePrismPropertyRealValueFromMap((MapXNode) xnode, null, propertyDef);
        } else {
            throw new IllegalArgumentException("Cannot parse property value from " + xnode);
        }
    }

    /**
     * Parses an atomic value - i.e. something that could present a property
     * value, if such a property would exist.
     */
    public <T> T parseAtomicValue(XNode xnode, QName typeName) throws SchemaException {
        if (xnode instanceof RootXNode) {
            return parseAtomicValue(((RootXNode) xnode).getSubnode(), typeName);
        } else if (xnode instanceof PrimitiveXNode<?>) {
            return parseAtomicValueFromPrimitive((PrimitiveXNode<T>) xnode, typeName);
        } else if (xnode instanceof MapXNode) {
            return parsePrismPropertyRealValueFromMap((MapXNode) xnode, typeName, null);
        } else {
            throw new IllegalArgumentException("Cannot parse atomic value from " + xnode);
        }
    }

    private <T> T parseAtomicValueFromPrimitive(PrimitiveXNode<T> xprim, QName typeName) throws SchemaException {
        return parseAtomicValueFromPrimitive(xprim, null, typeName);
    }

    private <T> T parseAtomicValueFromPrimitive(PrimitiveXNode<T> xprim, PrismPropertyDefinition def,
            QName typeName) throws SchemaException {
        T realValue = null;
        if (ItemPathType.COMPLEX_TYPE.equals(typeName)) {
            return (T) parseItemPathType(xprim);
        } else if (ProtectedStringType.COMPLEX_TYPE.equals(typeName)) {
            return (T) parseProtectedTypeFromPrimitive(xprim);
        } else if (getBeanConverter().canProcess(typeName) && !typeName.equals(PolyStringType.COMPLEX_TYPE)
                && !typeName.equals(ItemPathType.COMPLEX_TYPE)) {
            // Primitive elements may also have complex Java representations (e.g. enums)
            return getBeanConverter().unmarshallPrimitive(xprim, typeName);
        } else if (def != null && def.isRuntimeSchema() && def.getAllowedValues() != null
                && def.getAllowedValues().size() > 0) {
            //TODO: ugly hack to support enum in extension schemas --- need to be fixed

            realValue = xprim.getParsedValue(DOMUtil.XSD_STRING);
            if (!isAllowed(realValue, def.getAllowedValues())) {
                if (isStrict()) {
                    throw new SchemaException("Illegal value found in property " + xprim + ". Allowed values are: "
                            + def.getAllowedValues());
                } else {
                    // just skip the value
                    LOGGER.error("Skipping unknown value of type {}. Value: {}", typeName, xprim.getStringValue());
                    return null;
                }
            }

        } else {
            try {
                realValue = xprim.getParsedValue(typeName);
            } catch (SchemaException e) {
                if (isStrict()) {
                    throw e;
                } else {
                    // just skip the value
                    LoggingUtils.logException(LOGGER,
                            "Couldn't parse primitive value of type {}. Value: {}.\nDefinition: {}", e, typeName,
                            xprim.getStringValue(), def != null ? def.debugDump() : "(null)");
                    return null;
                }
            }
        }

        if (realValue == null) {
            return realValue;
        }

        if (realValue instanceof PolyStringType) {
            PolyStringType polyStringType = (PolyStringType) realValue;
            realValue = (T) new PolyString(polyStringType.getOrig(), polyStringType.getNorm());
        }

        if (!(realValue instanceof PolyString) && typeName.equals(PolyStringType.COMPLEX_TYPE)) {
            String val = (String) realValue;
            realValue = (T) new PolyString(val);
        }

        PrismUtil.recomputeRealValue(realValue, prismContext);
        return realValue;
    }

    private <T> boolean isAllowed(T realValue, Collection<DisplayableValue<T>> collection) {
        for (DisplayableValue<T> o : collection) {
            if (realValue.equals(o.getValue())) {
                return true;
            }
        }
        return false;
    }

    private ProtectedStringType parseProtectedTypeFromPrimitive(PrimitiveXNode xPrim) throws SchemaException {
        String clearValue = (String) xPrim.getParsedValue(DOMUtil.XSD_STRING);
        ProtectedStringType protectedString = new ProtectedStringType();
        protectedString.setClearValue(clearValue);
        return protectedString;
    }

    /**
     * This method is called either with a type name only, or with a property
     * definition only, or with both. Property definition is useful to correctly
     * formulate exception message.
     */
    private <T> T parsePrismPropertyRealValueFromMap(MapXNode xmap, QName typeName,
            PrismPropertyDefinition<T> propertyDefinition) throws SchemaException {
        if (typeName == null) {
            if (propertyDefinition == null) {
                throw new IllegalArgumentException(
                        "Couldn't parse prism property real value because of missing type name and property definition");
            }
            typeName = propertyDefinition.getTypeName();
        }
        if (getBeanConverter().canProcess(typeName)) {
            return getBeanConverter().unmarshall(xmap, typeName);
        } else {
            if (propertyDefinition != null) {
                if (propertyDefinition.isRuntimeSchema()) {
                    throw new SchemaException(
                            "Complex run-time properties are not supported: type " + typeName + " from " + xmap);
                } else {
                    throw new SystemException("Cannot parse compile-time property " + propertyDefinition.getName()
                            + " type " + typeName + " from " + xmap);
                }
            } else {
                throw new SchemaException(
                        "Couldn't parse property real value with type " + typeName + " from " + xmap);
            }
        }
    }

    public PrismPropertyValue parsePrismPropertyFromGlobalXNodeValue(Entry<QName, XNode> entry)
            throws SchemaException {
        Validate.notNull(entry);

        QName globalElementName = entry.getKey();
        if (globalElementName == null) {
            throw new SchemaException("No global element name to look for");
        }
        ItemDefinition itemDefinition = prismContext.getSchemaRegistry()
                .resolveGlobalItemDefinition(globalElementName);
        if (itemDefinition == null) {
            throw new SchemaException("No definition for item " + globalElementName);
        }

        if (itemDefinition instanceof PrismPropertyDefinition) {
            PrismProperty prismProperty = parsePrismProperty(entry.getValue(), globalElementName,
                    (PrismPropertyDefinition) itemDefinition);
            if (prismProperty.size() > 1) {
                throw new SchemaException(
                        "Retrieved more than one value from globally defined element " + globalElementName);
            } else if (prismProperty.size() == 0) {
                return null;
            } else {
                return (PrismPropertyValue) prismProperty.getValues().get(0);
            }
        } else {
            throw new IllegalArgumentException(
                    "Parsing global elements with definitions other than PrismPropertyDefinition is not supported yet: element = "
                            + globalElementName + " definition kind = "
                            + itemDefinition.getClass().getSimpleName());
        }
    }

    private ItemPathType parseItemPathType(PrimitiveXNode itemPath) throws SchemaException {
        ItemPath path = (ItemPath) itemPath.getParsedValue(ItemPath.XSD_TYPE);
        ItemPathType itemPathType = new ItemPathType(path);
        return itemPathType;
    }

    //    private <T> void parseProtectedType(ProtectedDataType<T> protectedType, MapXNode xmap) throws SchemaException {
    //        XNode xEncryptedData = xmap.get(ProtectedDataType.F_ENCRYPTED_DATA);
    //        if (xEncryptedData != null) {
    //            if (!(xEncryptedData instanceof MapXNode)) {
    //                throw new SchemaException("Cannot parse encryptedData from "+xEncryptedData);
    //            }
    //            EncryptedDataType encryptedDataType = getBeanConverter().unmarshall((MapXNode)xEncryptedData, EncryptedDataType.class);
    //            protectedType.setEncryptedData(encryptedDataType);
    //        } else {
    //            // Check for legacy EncryptedData
    //            XNode xLegacyEncryptedData = xmap.get(ProtectedDataType.F_XML_ENC_ENCRYPTED_DATA);
    //            if (xLegacyEncryptedData != null) {
    //                if (!(xLegacyEncryptedData instanceof MapXNode)) {
    //                    throw new SchemaException("Cannot parse EncryptedData from "+xEncryptedData);
    //                }
    //                MapXNode xConvertedEncryptedData = (MapXNode) xLegacyEncryptedData.cloneTransformKeys(new Transformer<QName>() {
    //                    @Override
    //                    public QName transform(QName in) {
    //                        String elementName = StringUtils.uncapitalize(in.getLocalPart());
    //                        if (elementName.equals("type")) {
    //                            // this is rubbish, we don't need it, we don't want it
    //                            return null;
    //                        }
    //                        return new QName(null, elementName);
    //                    }
    //                });
    //                EncryptedDataType encryptedDataType = getBeanConverter().unmarshall(xConvertedEncryptedData, EncryptedDataType.class);
    //                protectedType.setEncryptedData(encryptedDataType);
    //            }
    //        }
    //        // protected data empty..check for clear value
    //        if (protectedType.isEmpty()){
    //            XNode xClearValue = xmap.get(ProtectedDataType.F_CLEAR_VALUE);
    //            if (xClearValue == null){
    //               //TODO: try to use common namespace (only to be compatible with previous versions)
    //               //FIXME maybe add some warning, info...
    //               xClearValue = xmap.get(new QName(ProtectedDataType.F_CLEAR_VALUE.getLocalPart()));
    //            }
    //            if (xClearValue == null){
    //               return;
    //            }
    //            if (!(xClearValue instanceof PrimitiveXNode)){
    //                //this is maybe not good..
    //                throw new SchemaException("Cannot parse clear value from " + xClearValue);
    //            }
    //            // TODO: clearValue
    //            T clearValue = (T) ((PrimitiveXNode)xClearValue).getParsedValue(DOMUtil.XSD_STRING);
    //            protectedType.setClearValue(clearValue);
    //        }
    //
    //    }
    public static <T> PrismProperty<T> parsePrismPropertyRaw(XNode xnode, QName itemName, PrismContext prismContext)
            throws SchemaException {
        if (xnode instanceof ListXNode) {
            return parsePrismPropertyRaw((ListXNode) xnode, itemName, prismContext);
        } else {
            PrismProperty<T> property = new PrismProperty<T>(itemName, prismContext);
            PrismPropertyValue<T> pval = PrismPropertyValue.createRaw(xnode);
            property.add(pval);
            return property;
        }
    }

    private static <T> PrismProperty<T> parsePrismPropertyRaw(ListXNode xlist, QName itemName,
            PrismContext prismContext) throws SchemaException {
        PrismProperty<T> property = new PrismProperty<T>(itemName, prismContext);
        for (XNode xsubnode : xlist) {
            PrismPropertyValue<T> pval = PrismPropertyValue.createRaw(xsubnode);
            property.add(pval);
        }
        return property;
    }
    //endregion

    //region Parsing prism references
    public PrismReference parsePrismReference(XNode xnode, QName itemName,
            PrismReferenceDefinition referenceDefinition) throws SchemaException {
        if (xnode instanceof ListXNode) {
            return parsePrismReferenceFromList((ListXNode) xnode, itemName, referenceDefinition);
        } else if (xnode instanceof MapXNode) {
            return parsePrismReferenceFromMap((MapXNode) xnode, itemName, referenceDefinition);
        } else if (xnode instanceof PrimitiveXNode) {
            return referenceDefinition.instantiate();
        } else {
            throw new IllegalArgumentException("Cannot parse reference from " + xnode);
        }
    }

    private PrismReference parsePrismReferenceFromList(ListXNode xlist, QName itemName,
            PrismReferenceDefinition referenceDefinition) throws SchemaException {
        if (xlist == null || xlist.isEmpty()) {
            return null;
        }
        PrismReference ref = referenceDefinition.instantiate();

        if (!referenceDefinition.isMultiValue() && xlist.size() > 1) {
            throw new SchemaException("Attempt to store multiple values in single-valued reference " + itemName);
        }

        for (XNode subnode : xlist) {
            parsePrismReferenceValueFromXNode(ref, subnode, referenceDefinition, itemName);
        }
        return ref;
    }

    private PrismReference parsePrismReferenceFromMap(MapXNode xmap, QName itemName,
            PrismReferenceDefinition referenceDefinition) throws SchemaException {
        PrismReference ref = referenceDefinition.instantiate();
        parsePrismReferenceValueFromXNode(ref, xmap, referenceDefinition, itemName);
        return ref;
    }

    private void parsePrismReferenceValueFromXNode(PrismReference ref, XNode subnode,
            PrismReferenceDefinition referenceDefinition, QName itemName) throws SchemaException {
        /*
         *  We distinguish between "real" references and composite objects by
         *  (1) looking at type QName of XNode passed (whether it's ObjectType or ObjectReferenceType)
         *  (2) comparing itemName and name from reference definition - e.g. linkRef vs. link
         */
        boolean isComposite;
        if (subnode.getTypeQName() != null) {
            QName typeName = subnode.getTypeQName();
            if (prismContext != null) {
                ItemDefinition definition = prismContext.getSchemaRegistry().findItemDefinitionByType(typeName);
                isComposite = definition instanceof PrismObjectDefinition;
            } else {
                isComposite = PrismConstants.REFERENCE_TYPE_NAME.equals(typeName.getLocalPart());
            }
        } else {
            isComposite = !QNameUtil.match(itemName, referenceDefinition.getName());
        }

        if (isComposite) {
            // This is a composite object (complete object stored inside
            // reference)
            ref.add(parseReferenceAsCompositeObject(subnode, referenceDefinition));
        } else {
            // This is "real" reference (oid type and nothing more)
            ref.add(parseReferenceValue(subnode, referenceDefinition));
        }
    }

    public PrismReferenceValue parseReferenceValue(XNode xnode, PrismReferenceDefinition referenceDefinition)
            throws SchemaException {
        if (xnode instanceof MapXNode) {
            return parseReferenceValue((MapXNode) xnode, referenceDefinition);
        } else {
            throw new IllegalArgumentException("Cannot parse reference from " + xnode);
        }
    }

    public PrismReferenceValue parseReferenceValue(MapXNode xmap, PrismReferenceDefinition referenceDefinition)
            throws SchemaException {
        String oid = xmap.getParsedPrimitiveValue(XNode.KEY_REFERENCE_OID, DOMUtil.XSD_STRING);
        PrismReferenceValue refVal = new PrismReferenceValue(oid);

        QName type = xmap.getParsedPrimitiveValue(XNode.KEY_REFERENCE_TYPE, DOMUtil.XSD_QNAME);
        if (type == null) {
            type = referenceDefinition.getTargetTypeName();
            if (type == null) {
                throw new SchemaException("Target type specified neither in reference nor in the schema");
            }
        } else {
            if (StringUtils.isBlank(type.getNamespaceURI())) {
                // resolve type without namespace (only when prismContext is known)
                if (prismContext == null) {
                    throw new SchemaException(
                            "Couldn't parse unqualified type name '" + type + "' without prismContext");
                }
                type = prismContext.getSchemaRegistry().resolveUnqualifiedTypeName(type);
            }

            QName defTargetType = referenceDefinition.getTargetTypeName();

            if (defTargetType != null && !QNameUtil.match(defTargetType, type)) {
                //one more check - if the type is not a subtype of the schema type
                Class clazz = qnameToClass(type);
                if (clazz == null) {
                    throw new SchemaException("Unknown target type: " + type);
                }
                if (!qnameToClass(defTargetType).isAssignableFrom(clazz)) {
                    throw new SchemaException("Target type specified in reference (" + type
                            + ") does not match target type in schema (" + defTargetType + ")");
                }
            }
        }

        PrismObjectDefinition<Objectable> objectDefinition = getSchemaRegistry().findObjectDefinitionByType(type);
        if (objectDefinition == null) {
            throw new SchemaException("No definition for type " + type + " in reference");
        }
        refVal.setTargetType(type);

        QName relationAttribute = xmap.getParsedPrimitiveValue(XNode.KEY_REFERENCE_RELATION, DOMUtil.XSD_QNAME);
        refVal.setRelation(relationAttribute);

        refVal.setDescription(
                (String) xmap.getParsedPrimitiveValue(XNode.KEY_REFERENCE_DESCRIPTION, DOMUtil.XSD_STRING));

        refVal.setFilter(parseFilter(xmap.get(XNode.KEY_REFERENCE_FILTER)));

        XNode xnodeForTargetName = xmap.get(XNode.KEY_REFERENCE_TARGET_NAME);
        if (xnodeForTargetName != null) {
            Class targetNameClass = PolyStringType.class;
            if (xnodeForTargetName instanceof PrimitiveXNode
                    && ((PrimitiveXNode) xnodeForTargetName).getValue() instanceof PolyString) {
                targetNameClass = PolyString.class; // targetName in audit report comes as PolyString
            }
            Object o = getBeanConverter().unmarshall(xnodeForTargetName, targetNameClass);
            // working around polystring-related type mess in unmarshaller
            if (o instanceof PolyString) {
                refVal.setTargetName((PolyString) o);
            } else if (o instanceof PolyStringType) {
                refVal.setTargetName((PolyStringType) o);
            } else {
                throw new IllegalStateException("targetName is not a polystring, it's a " + o.getClass().getName());
            }
        }

        XNode xrefObject = xmap.get(XNode.KEY_REFERENCE_OBJECT);
        if (xrefObject != null) {
            if (!(xrefObject instanceof MapXNode)) {
                throw new SchemaException("Cannot parse object from " + xrefObject);
            }
            PrismObject<Objectable> object = parseObject((MapXNode) xrefObject, objectDefinition);
            setReferenceObject(refVal, object);
        }

        return refVal;
    }

    private void setReferenceObject(PrismReferenceValue refVal, PrismObject<Objectable> object)
            throws SchemaException {
        refVal.setObject(object);
        if (object.getOid() != null) {
            if (refVal.getOid() == null) {
                refVal.setOid(object.getOid());
            } else {
                if (!refVal.getOid().equals(object.getOid())) {
                    throw new SchemaException("OID in reference (" + refVal.getOid()
                            + ") does not match OID in composite object (" + object.getOid() + ")");
                }
            }
        }
        QName objectTypeName = object.getDefinition().getTypeName();
        if (refVal.getTargetType() == null) {
            refVal.setTargetType(objectTypeName);
        } else {
            if (!refVal.getTargetType().equals(objectTypeName)) {
                throw new SchemaException("Target type in reference (" + refVal.getTargetType()
                        + ") does not match OID in composite object (" + objectTypeName + ")");
            }
        }
    }

    private PrismReferenceValue parseReferenceAsCompositeObject(XNode xnode,
            PrismReferenceDefinition referenceDefinition) throws SchemaException {
        if (xnode instanceof MapXNode) {
            return parseReferenceAsCompositeObject((MapXNode) xnode, referenceDefinition);
        } else {
            throw new IllegalArgumentException("Cannot parse reference composite object from " + xnode);
        }
    }

    private PrismReferenceValue parseReferenceAsCompositeObject(MapXNode xmap,
            PrismReferenceDefinition referenceDefinition) throws SchemaException {
        QName targetTypeName = referenceDefinition.getTargetTypeName();
        PrismObjectDefinition<Objectable> objectDefinition = null;
        if (xmap.getTypeQName() != null) {
            objectDefinition = getSchemaRegistry().findObjectDefinitionByType(xmap.getTypeQName());
        }
        if (objectDefinition == null && targetTypeName != null) {
            objectDefinition = prismContext.getSchemaRegistry().findObjectDefinitionByType(targetTypeName);
        }
        if (objectDefinition == null) {
            throw new SchemaException("No object definition for composite object in reference element "
                    + referenceDefinition.getCompositeObjectElementName());
        }

        PrismObject<Objectable> compositeObject = null;
        try {
            compositeObject = parseObject(xmap, objectDefinition);
        } catch (SchemaException e) {
            throw new SchemaException(e.getMessage() + " while parsing composite object in reference element "
                    + referenceDefinition.getCompositeObjectElementName(), e);
        }

        PrismReferenceValue refVal = new PrismReferenceValue();
        setReferenceObject(refVal, compositeObject);
        referenceDefinition.setComposite(true);
        return refVal;
    }

    private SearchFilterType parseFilter(XNode xnode) throws SchemaException {
        if (xnode == null) {
            return null;
        }
        // TODO: is this warning needed?
        if (xnode.isEmpty()) {
            //System.out.println("Empty filter. Skipping parsing.");
            return null;
        }
        return SearchFilterType.createFromXNode(xnode, prismContext);
    }

    private Class qnameToClass(QName type) {
        Class c = getSchemaRegistry().determineCompileTimeClass(type);
        if (c == null) {
            throw new IllegalStateException("No class for " + type);
        }
        return c;
    }
    //endregion

    //region Resolving definitions
    public <T extends Containerable> ItemDefinition locateItemDefinition(
            PrismContainerDefinition<T> containerDefinition, QName elementQName, XNode xnode)
            throws SchemaException {
        ItemDefinition def = containerDefinition.findItemDefinition(elementQName);
        if (def != null) {
            return def;
        }

        def = resolveDynamicItemDefinition(containerDefinition, elementQName, xnode);
        if (def != null) {
            return def;
        }

        if (containerDefinition.isRuntimeSchema()) {
            // Try to locate global definition in any of the schemas
            def = resolveGlobalItemDefinition(containerDefinition, elementQName, xnode);
        }
        return def;
    }

    private ItemDefinition resolveDynamicItemDefinition(ItemDefinition parentDefinition, QName elementName,
            XNode xnode) throws SchemaException {
        if (xnode == null) {
            return null;
        }
        QName typeName = xnode.getTypeQName();
        if (typeName == null) {
            if (xnode instanceof ListXNode) {
                // there may be type definitions in individual list members
                for (XNode subnode : ((ListXNode) xnode)) {
                    ItemDefinition subdef = resolveDynamicItemDefinition(parentDefinition, elementName, subnode);
                    // TODO: make this smarter, e.g. detect conflicting type definitions
                    if (subdef != null) {
                        return subdef;
                    }
                }
            }
        }
        if (typeName == null) {
            return null;
        }
        PrismPropertyDefinition propDef = new PrismPropertyDefinition(elementName, typeName, prismContext);
        Integer maxOccurs = xnode.getMaxOccurs();
        if (maxOccurs != null) {
            propDef.setMaxOccurs(maxOccurs);
        } else {
            // Make this multivalue by default, this is more "open"
            propDef.setMaxOccurs(-1);
        }
        propDef.setDynamic(true);
        return propDef;
    }

    private <T extends Containerable> ItemDefinition resolveGlobalItemDefinition(
            PrismContainerDefinition<T> containerDefinition, QName elementQName, XNode xnode)
            throws SchemaException {
        return prismContext.getSchemaRegistry().resolveGlobalItemDefinition(elementQName);
    }
    //endregion

    //region Parsing general items and beans
    /**
     * This gets definition of an unspecified type. It has to find the right
     * method to call. Value elements have the same element name. They may be
     * elements of a property or a container.
     */
    public <IV extends PrismValue, ID extends ItemDefinition> Item<IV, ID> parseItem(XNode xnode, QName itemName,
            ItemDefinition itemDef) throws SchemaException {
        if (itemDef == null) {
            // Assume property in a container with runtime definition
            return (Item<IV, ID>) parsePrismPropertyRaw(xnode, itemName, prismContext);
        }
        if (itemDef instanceof PrismObjectDefinition) {
            return parseObject(xnode, itemName, (PrismObjectDefinition) itemDef);
        } else if (itemDef instanceof PrismContainerDefinition) {
            return (Item<IV, ID>) parseContainerInternal(xnode, itemName, (PrismContainerDefinition<?>) itemDef);
        } else if (itemDef instanceof PrismPropertyDefinition) {
            return (Item<IV, ID>) parsePrismProperty(xnode, itemName, (PrismPropertyDefinition) itemDef);
        } else if (itemDef instanceof PrismReferenceDefinition) {
            return (Item<IV, ID>) parsePrismReference(xnode, itemName, (PrismReferenceDefinition) itemDef);
        } else {
            throw new IllegalArgumentException(
                    "Attempt to parse unknown definition type " + itemDef.getClass().getName());
        }
    }

    /**
     * Parses (almost) anything: either an item with a definition, or an atomic
     * (i.e. property-like) value. Does not care for schemaless items!
     *
     * @param node
     * @return either Item or a unmarshalled bean value
     * @throws SchemaException
     */
    public Object parseAnyData(XNode node) throws SchemaException {
        // is the type name explicitly specified? (if not, guess that we have a string)
        QName typeName = getExplicitType(node);
        if (typeName == null && node instanceof PrimitiveXNode) {
            typeName = DOMUtil.XSD_STRING;
        }
        if (typeName != null) {
            ItemDefinition itemDefinition = getSchemaRegistry().findItemDefinitionByType(typeName);
            if (itemDefinition != null) {
                return parseItem(node, getElementName(node, itemDefinition), itemDefinition);
            } else {
                return parseAtomicValue(node, typeName);
            }
        } else {
            // if type name is not known, we have to derive it from the element name
            if (!(node instanceof RootXNode)) {
                throw new SchemaException(
                        "Couldn't parse general object with no type name and no root element name: " + node);
            }
            QName elementName = ((RootXNode) node).getRootElementName();
            ItemDefinition itemDefinition = getSchemaRegistry().findItemDefinitionByElementName(elementName);
            if (itemDefinition == null) {
                throw new SchemaException(
                        "Couldn't parse general object with no type name and unknown element name: " + elementName);
            }
            return parseItem(((RootXNode) node).getSubnode(), elementName, itemDefinition);
        }
    }

    // emulates JAXB unmarshal method
    public <T> T parseAnyValue(XNode node) throws SchemaException {
        // is the type name explicitly specified? (if not, guess that we have a string)
        QName typeName = getExplicitType(node);
        if (typeName == null && node instanceof PrimitiveXNode) {
            typeName = DOMUtil.XSD_STRING;
        }
        if (typeName != null) {
            ItemDefinition itemDefinition = getSchemaRegistry().findItemDefinitionByType(typeName);
            if (itemDefinition != null) {
                Item item = parseItem(node, getElementName(node, itemDefinition), itemDefinition);
                return (T) getItemRealValue(item);
            } else {
                return parseAtomicValue(node, typeName);
            }
        } else {
            // if type name is not known, we have to derive it from the element name
            if (!(node instanceof RootXNode)) {
                throw new SchemaException(
                        "Couldn't parse general object with no type name and no root element name: " + node);
            }
            QName elementName = ((RootXNode) node).getRootElementName();
            ItemDefinition itemDefinition = getSchemaRegistry().findItemDefinitionByElementName(elementName);
            if (itemDefinition == null) {
                throw new SchemaException(
                        "Couldn't parse general object with no type name and unknown element name: " + elementName);
            }
            Item item = parseItem(((RootXNode) node).getSubnode(), elementName, itemDefinition);
            return (T) getItemRealValue(item);
        }
    }

    // experimental
    public <T> JAXBElement<T> parseAnyValueAsJAXBElement(XNode node) throws SchemaException {
        // is the type name explicitly specified? (if not, guess that we have a string)
        QName typeName = getExplicitType(node);
        if (typeName == null && node instanceof PrimitiveXNode) {
            typeName = DOMUtil.XSD_STRING;
        }
        if (typeName != null) {
            ItemDefinition itemDefinition = getSchemaRegistry().findItemDefinitionByType(typeName);
            if (itemDefinition != null) {
                Item item = parseItem(node, getElementName(node, itemDefinition), itemDefinition);
                return getItemValueAsJAXBElement(item);
            } else {
                Object o = parseAtomicValue(node, typeName);
                if (o instanceof JAXBElement) {
                    return (JAXBElement) o;
                } else {
                    if (!(node instanceof RootXNode)) {
                        throw new SchemaException(
                                "Couldn't represent an object with no root element name and no item definition as JAXBElement: "
                                        + node);
                    }
                    QName elementName = ((RootXNode) node).getRootElementName();
                    return new JAXBElement(elementName, Object.class, o); // or o.getClass?
                }
            }
        } else {
            // if type name is not known, we have to derive it from the element name
            if (!(node instanceof RootXNode)) {
                throw new SchemaException(
                        "Couldn't parse general object with no type name and no root element name: " + node);
            }
            QName elementName = ((RootXNode) node).getRootElementName();
            ItemDefinition itemDefinition = getSchemaRegistry().findItemDefinitionByElementName(elementName);
            if (itemDefinition == null) {
                throw new SchemaException(
                        "Couldn't parse general object with no type name and unknown element name: " + elementName);
            }
            Item item = parseItem(((RootXNode) node).getSubnode(), elementName, itemDefinition);
            return getItemValueAsJAXBElement(item);
        }
    }

    private Object getItemRealValue(Item item) throws SchemaException {
        if (item.size() == 0) {
            return null;
        } else if (item.size() > 1) {
            throw new SchemaException("Trying to get value from multi-valued item: " + item);
        } else {
            PrismValue value = (PrismValue) item.getValues().get(0);
            if (value instanceof PrismPropertyValue) {
                return ((PrismPropertyValue) value).getValue();
            } else if (value instanceof PrismContainerValue) {
                return ((PrismContainerValue) value).getValue();
            } else if (value instanceof PrismReferenceValue) {
                return value; // TODO what to return in this case?
            } else {
                throw new IllegalStateException(
                        "Unexpected value type: " + (value != null ? value.getClass() : "(null)"));
            }
        }
    }

    private <T> JAXBElement<T> getItemValueAsJAXBElement(Item item) throws SchemaException {
        Object v = getItemRealValue(item);
        if (v == null) {
            return null;
        } else {
            return (JAXBElement) new JAXBElement<>(item.getElementName(), Object.class, v); // or v.getClass?
        }
    }

    private QName getElementName(XNode node, ItemDefinition itemDefinition) {
        if (node instanceof RootXNode) {
            return ((RootXNode) node).getRootElementName();
        } else if (itemDefinition != null) {
            return itemDefinition.getName();
        } else {
            throw new IllegalStateException(
                    "Couldn't determine element name - neither from XNode nor from itemDefinition");
        }
    }

    private QName getExplicitType(XNode node) {
        if (node.getTypeQName() != null) {
            return node.getTypeQName();
        } else if (node instanceof RootXNode) {
            RootXNode rootXNode = (RootXNode) node;
            if (rootXNode.getSubnode() != null && rootXNode.getSubnode().getTypeQName() != null) {
                return rootXNode.getSubnode().getTypeQName();
            }
        }
        return null;
    }

    //    public <T> T parseBean(XNode xnode, Class<T> clazz) throws SchemaException {
    //        MapXNode mapXNode;
    //        if (xnode instanceof RootXNode) {
    //            XNode subnode = ((RootXNode) xnode).getSubnode();
    //            if (!(subnode instanceof MapXNode)) {
    //                throw new SchemaException("Couldn't parse " + clazz + " bean from " + xnode + ", as it does not contain a MapXNode");
    //            }
    //            mapXNode = (MapXNode) subnode;
    //        } else if (xnode instanceof MapXNode) {
    //            mapXNode = (MapXNode) xnode;
    //        } else {
    //            throw new SchemaException("Couldn't parse " + clazz + " bean from " + xnode + ", as it is not a MapXNode");
    //        }
    //        return getBeanConverter().unmarshall(mapXNode, clazz);
    //    }
    //endregion
    //region Serialization
    // --------------------------
    // -- SERIALIZATION
    // --------------------------
    public <O extends Objectable> RootXNode serializeObject(PrismObject<O> object) throws SchemaException {
        XNodeSerializer serializer = createSerializer();
        return serializer.serializeObject(object);
    }

    public <O extends Objectable> RootXNode serializeObject(PrismObject<O> object,
            boolean serializeCompositeObjects) throws SchemaException {
        return serializeObject(object, serializeCompositeObjects, null);
    }

    public <O extends Objectable> RootXNode serializeObject(PrismObject<O> object,
            boolean serializeCompositeObjects, SerializationContext ctx) throws SchemaException {
        XNodeSerializer serializer = createSerializer();
        serializer.setSerializeCompositeObjects(serializeCompositeObjects);
        return serializer.serializeObject(object, ctx);
    }

    //   public <C extends Containerable> RootXNode serializeContainerValueRoot(PrismContainerValue<C> cval) throws SchemaException {
    //      XNodeSerializer serializer = createSerializer();
    //      return serializer.serializeContainerValueAsRoot(cval);
    //   }
    //
    //    public <C extends Containerable> RootXNode serializeContainerValueRoot(PrismContainerValue<C> cval, QName elementName) throws SchemaException {
    //        XNodeSerializer serializer = createSerializer();
    //        return serializer.serializeContainerValueAsRoot(cval, elementName);
    //    }
    public <IV extends PrismValue, ID extends ItemDefinition> XNode serializeItem(Item<IV, ID> item)
            throws SchemaException {
        XNodeSerializer serializer = createSerializer();
        return serializer.serializeItem(item);
    }

    //    public <T> RootXNode serializePropertyValueAsRoot(PrismPropertyValue<T> propval, QName elementName) throws SchemaException {
    //        XNodeSerializer serializer = createSerializer();
    //        return serializer.serializePropertyValueAsRoot(propval, elementName);
    //    }
    public RootXNode serializeItemValueAsRoot(PrismValue pval, QName elementName) throws SchemaException {
        XNodeSerializer serializer = createSerializer();
        return serializer.serializeItemValueAsRoot(pval, elementName);
    }

    public XNode serializeItemValue(PrismValue pval) throws SchemaException {
        XNodeSerializer serializer = createSerializer();
        return serializer.serializeItemValue(pval, null);
    }

    public <IV extends PrismValue, ID extends ItemDefinition> RootXNode serializeItemAsRoot(Item<IV, ID> item)
            throws SchemaException {
        XNodeSerializer serializer = createSerializer();
        return serializer.serializeItemAsRoot(item);
    }

    public XNodeSerializer createSerializer() {
        return new XNodeSerializer(PrismUtil.getBeanConverter(prismContext));
    }

    public RootXNode serializeAnyData(Object object, SerializationContext ctx) throws SchemaException {
        return serializeAnyData(object, null, ctx);
    }

    public RootXNode serializeAnyData(Object object, QName defaultRootElementName, SerializationContext ctx)
            throws SchemaException {
        Validate.notNull(object);
        if (object instanceof Item) {
            RootXNode root = serializeItemAsRoot((Item) object);
            return root;
        } else {
            Validate.notNull(defaultRootElementName, "rootElementName must be specified for non-Item objects");
            XNode valueXNode = getBeanConverter().marshall(object, ctx);
            QName typeQName = JAXBUtil.getTypeQName(object.getClass());
            if (valueXNode.getTypeQName() == null) {
                if (typeQName != null) {
                    valueXNode.setTypeQName(typeQName);
                } else {
                    throw new SchemaException("No type QName for class " + object.getClass());
                }
            }
            return new RootXNode(defaultRootElementName, valueXNode);
        }
    }

    // TODO: very preliminary implementation - does not care for special cases (e.g. PolyString etc)
    public RootXNode serializeAtomicValue(Object object, QName elementName, SerializationContext ctx)
            throws SchemaException {
        XNode valueXNode = getBeanConverter().marshall(object, ctx);
        QName typeQName = JAXBUtil.getTypeQName(object.getClass());
        if (typeQName != null) {
            valueXNode.setTypeQName(typeQName);
        } else {
            throw new SchemaException("No type QName for class " + object.getClass());
        }
        return new RootXNode(elementName, valueXNode);
    }

    public RootXNode serializeAtomicValue(JAXBElement<?> element) throws SchemaException {
        Validate.notNull(element);
        return serializeAtomicValue(element.getValue(), element.getName(), null);
    }

    public boolean canSerialize(Object object) {
        if (object instanceof Item) {
            return true;
        } else {
            return getBeanConverter().canProcess(object.getClass());
        }
    }

    //endregion
    private PrismBeanConverter getBeanConverter() {
        if (mode == XNodeProcessorEvaluationMode.COMPAT) {
            return prismContext.getCompatModeBeanConverter();
        } else {
            return prismContext.getBeanConverter();
        }
    }

}