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

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2010-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;

import com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.prism.path.ObjectReferencePathSegment;
import com.evolveum.midpoint.prism.path.ParentPathSegment;
import com.evolveum.midpoint.prism.schema.PrismSchema;
import com.evolveum.midpoint.util.DOMUtil;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.QNameUtil;

import javax.xml.namespace.QName;

import org.apache.commons.lang.StringUtils;

import java.util.*;

/**
 * Definition of a property container.
 * <p/>
 * Property container groups properties into logical blocks. The reason for
 * grouping may be as simple as better understandability of data structure. But
 * the group usually means different meaning, source or structure of the data.
 * For example, the property container is frequently used to hold properties
 * that are dynamic, not fixed by a static schema. Such grouping also naturally
 * translates to XML and helps to "quarantine" such properties to avoid Unique
 * Particle Attribute problems.
 * <p/>
 * Property Container contains a set of (potentially multi-valued) properties.
 * The order of properties is not significant, regardless of the fact that it
 * may be fixed in the XML representation. In the XML representation, each
 * element inside Property Container must be either Property or a Property
 * Container.
 * <p/>
 * This class represents schema definition for property container. See
 * {@link Definition} for more details.
 *
 * @author Radovan Semancik
 */
public class PrismContainerDefinition<C extends Containerable> extends ItemDefinition<PrismContainer<C>> {

    private static final long serialVersionUID = -5068923696147960699L;

    protected ComplexTypeDefinition complexTypeDefinition;
    protected Class<C> compileTimeClass;

    /**
     * The constructors should be used only occasionally (if used at all).
     * Use the factory methods in the ResourceObjectDefintion instead.
     */
    public PrismContainerDefinition(QName name, ComplexTypeDefinition complexTypeDefinition,
            PrismContext prismContext) {
        this(name, complexTypeDefinition, prismContext, null);
    }

    public PrismContainerDefinition(QName name, ComplexTypeDefinition complexTypeDefinition,
            PrismContext prismContext, Class<C> compileTimeClass) {
        super(name, determineTypeName(complexTypeDefinition), prismContext);
        this.complexTypeDefinition = complexTypeDefinition;
        if (complexTypeDefinition == null) {
            isRuntimeSchema = true;
            super.setDynamic(true);
        } else {
            isRuntimeSchema = complexTypeDefinition.isXsdAnyMarker();
            super.setDynamic(isRuntimeSchema);
        }
        this.compileTimeClass = compileTimeClass;
    }

    private static QName determineTypeName(ComplexTypeDefinition complexTypeDefinition) {
        if (complexTypeDefinition == null) {
            // Property container without type: xsd:any
            // FIXME: this is kind of hack, but it works now
            return DOMUtil.XSD_ANY;
        }
        return complexTypeDefinition.getTypeName();
    }

    public Class<C> getCompileTimeClass() {
        if (compileTimeClass != null) {
            return compileTimeClass;
        }
        if (complexTypeDefinition == null) {
            return null;
        }
        return (Class<C>) complexTypeDefinition.getCompileTimeClass();
    }

    public void setCompileTimeClass(Class<C> compileTimeClass) {
        this.compileTimeClass = compileTimeClass;
    }

    protected String getSchemaNamespace() {
        return getName().getNamespaceURI();
    }

    public ComplexTypeDefinition getComplexTypeDefinition() {
        return complexTypeDefinition;
    }

    public void setComplexTypeDefinition(ComplexTypeDefinition complexTypeDefinition) {
        this.complexTypeDefinition = complexTypeDefinition;
    }

    @Override
    public boolean isAbstract() {
        if (super.isAbstract()) {
            return true;
        }
        if (complexTypeDefinition != null && complexTypeDefinition.isAbstract()) {
            return true;
        }
        return false;
    }

    /**
     * Returns true if the definition does not define specific items but it is just
     * a "wildcard" for any kind of item (usually represented as xsd:any type).
     */
    public boolean isWildcard() {
        if (getTypeName().equals(DOMUtil.XSD_ANY)) {
            return true;
        }
        //      if (complexTypeDefinition != null && complexTypeDefinition.isXsdAnyMarker()) {
        //         return true;
        //      }
        return false;
    }

    @Override
    public void revive(PrismContext prismContext) {
        if (this.prismContext != null) {
            return;
        }
        this.prismContext = prismContext;
        if (complexTypeDefinition != null) {
            complexTypeDefinition.revive(prismContext);
        }
    }

    /*
     * TODO clean this up: There are three definition-finding algorithms:
     *  - findItemDefinition (QName ...)
     *  - findItemDefinition (ItemPath ...)
     *  - findPropertyDefinition (ItemPath ...)
     *
     *  This has to be replaced by a single algorithm.
     */
    public <D extends ItemDefinition> D findItemDefinition(QName name, Class<D> clazz) {
        return findItemDefinition(name, clazz, false);
    }

    public <D extends ItemDefinition> D findItemDefinition(QName name, Class<D> clazz, boolean caseInsensitive) {
        if (clazz == null) {
            throw new IllegalArgumentException("type not specified while searching for " + name + " in " + this);
        }
        if (name == null) {
            throw new IllegalArgumentException("name not specified while searching in " + this);
        }

        D itemDefinition;
        if (complexTypeDefinition != null) {
            itemDefinition = complexTypeDefinition.findItemDefinition(name, clazz, caseInsensitive);
        } else {
            // xsd:any and similar dynamic definitions
            itemDefinition = null;
        }

        if (itemDefinition == null && isRuntimeSchema()) {
            itemDefinition = findRuntimeItemDefinition(name, null, clazz); // TODO what about case insensitive?
        }
        return itemDefinition;
    }

    private <D extends ItemDefinition> D findRuntimeItemDefinition(QName firstName, ItemPath rest, Class<D> clazz) {
        if (prismContext == null) {
            return null; // should not occur
        }
        ItemDefinition definition = prismContext.getSchemaRegistry().findItemDefinitionByElementName(firstName);
        if (definition == null) {
            return null;
        }
        if (rest != null && !rest.isEmpty()) {
            return (D) definition.findItemDefinition(rest, clazz);
        }
        // this is the last step of search
        if (clazz.isAssignableFrom(definition.getClass())) {
            return (D) definition;
        } else {
            return null;
        }
    }

    public <ID extends ItemDefinition> ID findItemDefinition(ItemPath path, Class<ID> clazz) {
        for (;;) {
            if (path.isEmpty()) {
                if (clazz.isAssignableFrom(PrismContainerDefinition.class)) {
                    return (ID) this;
                } else {
                    return null;
                }
            }
            ItemPathSegment first = path.first();
            if (first instanceof NameItemPathSegment) {
                QName firstName = ((NameItemPathSegment) first).getName();
                return findNamedItemDefinition(firstName, path.rest(), clazz);
            } else if (first instanceof IdItemPathSegment) {
                path = path.rest();
            } else if (first instanceof ParentPathSegment) {
                ComplexTypeDefinition parent = getSchemaRegistry()
                        .determineParentDefinition(getComplexTypeDefinition(), path.rest());
                return parent.findItemDefinition(path.tail(), clazz);
            } else if (first instanceof ObjectReferencePathSegment) {
                throw new IllegalStateException(
                        "Couldn't use '@' path segment in this context. PCD=" + getTypeName() + ", path=" + path);
            } else {
                throw new IllegalStateException("Unexpected path segment: " + first + " in " + path);
            }
        }
    }

    public <ID extends ItemDefinition> ID findNamedItemDefinition(QName firstName, ItemPath rest, Class<ID> clazz) {

        // we need to be compatible with older versions..soo if the path does
        // not contains qnames with namespaces defined (but the prefix was
        // specified) match definition according to the local name
        if (StringUtils.isEmpty(firstName.getNamespaceURI())) {
            for (ItemDefinition def : getDefinitions()) {
                if (QNameUtil.match(firstName, def.getName())) {
                    return (ID) def.findItemDefinition(rest, clazz);
                }
            }
        }

        for (ItemDefinition def : getDefinitions()) {
            if (firstName.equals(def.getName())) {
                return (ID) def.findItemDefinition(rest, clazz);
            }
        }

        if (isRuntimeSchema()) {
            return findRuntimeItemDefinition(firstName, rest, clazz);
        }

        return null;
    }

    public <ID extends ItemDefinition> ID findItemDefinition(QName name) {
        return (ID) findItemDefinition(name, ItemDefinition.class);
    }

    public <ID extends ItemDefinition> ID findItemDefinition(ItemPath path) {
        return (ID) findItemDefinition(path, ItemDefinition.class);
    }

    /**
     * Finds a PropertyDefinition by looking at the property name.
     * <p/>
     * Returns null if nothing is found.
     *
     * @param name property definition name
     * @return found property definition or null
     */
    public <T> PrismPropertyDefinition<T> findPropertyDefinition(QName name) {
        return findItemDefinition(name, PrismPropertyDefinition.class);
    }

    public <T> PrismPropertyDefinition<T> findPropertyDefinition(ItemPath path) {
        while (!path.isEmpty() && !(path.first() instanceof NameItemPathSegment)) {
            path = path.rest();
        }
        if (path.isEmpty()) {
            throw new IllegalArgumentException(
                    "Property path is empty while searching for property definition in " + this);
        }
        QName firstName = ((NameItemPathSegment) path.first()).getName();
        if (path.size() == 1) {
            return findPropertyDefinition(firstName);
        }
        PrismContainerDefinition pcd = findContainerDefinition(firstName);
        if (pcd == null) {
            throw new IllegalArgumentException("There is no " + firstName + " subcontainer in " + this);
        }
        return pcd.findPropertyDefinition(path.rest());
    }

    public PrismReferenceDefinition findReferenceDefinition(QName name) {
        return findItemDefinition(name, PrismReferenceDefinition.class);
    }

    public PrismReferenceDefinition findReferenceDefinition(ItemPath path) {
        return findItemDefinition(path, PrismReferenceDefinition.class);
    }

    /**
     * Finds an inner PropertyContainerDefinition by looking at the property container name.
     * <p/>
     * Returns null if nothing is found.
     *
     * @param name property container definition name
     * @return found property container definition or null
     */
    public <X extends Containerable> PrismContainerDefinition<X> findContainerDefinition(QName name) {
        return findItemDefinition(name, PrismContainerDefinition.class);
    }

    public <X extends Containerable> PrismContainerDefinition<X> findContainerDefinition(String name) {
        return findContainerDefinition(new QName(getNamespace(), name));
    }

    /**
     * Finds an inner PropertyContainerDefinition by following the property container path.
     * <p/>
     * Returns null if nothing is found.
     *
     * @param path property container path
     * @return found property container definition or null
     */
    public PrismContainerDefinition findContainerDefinition(ItemPath path) {
        return findItemDefinition(path, PrismContainerDefinition.class);
    }

    /**
     * Returns set of property definitions.
     * <p/>
     * WARNING: This may return definitions from the associated complex type.
     * Therefore changing the returned set may influence also the complex type definition.
     * <p/>
     * The set contains all property definitions of all types that were parsed.
     * Order of definitions is insignificant.
     *
     * @return set of definitions
     */
    public List<? extends ItemDefinition> getDefinitions() {
        if (complexTypeDefinition == null) {
            // e.g. for xsd:any containers
            // FIXME
            return new ArrayList<ItemDefinition>();
        }
        return complexTypeDefinition.getDefinitions();
    }

    /**
     * Returns set of property definitions.
     * <p/>
     * The set contains all property definitions of all types that were parsed.
     * Order of definitions is insignificant.
     * <p/>
     * The returned set is immutable! All changes may be lost.
     *
     * @return set of definitions
     */
    public List<PrismPropertyDefinition> getPropertyDefinitions() {
        List<PrismPropertyDefinition> props = new ArrayList<PrismPropertyDefinition>();
        for (ItemDefinition def : complexTypeDefinition.getDefinitions()) {
            if (def instanceof PrismPropertyDefinition) {
                props.add((PrismPropertyDefinition) def);
            }
        }
        return props;
    }

    /**
     * Create property container instance with a default name.
     * <p/>
     * This is a preferred way how to create property container.
     */
    @Override
    public PrismContainer<C> instantiate() {
        return instantiate(getName());
    }

    /**
     * Create property container instance with a specified name and element.
     * <p/>
     * This is a preferred way how to create property container.
     */
    @Override
    public PrismContainer<C> instantiate(QName elementName) {
        elementName = addNamespaceIfApplicable(elementName);
        return new PrismContainer<C>(elementName, this, prismContext);
    }

    @Override
    public ContainerDelta<C> createEmptyDelta(ItemPath path) {
        return new ContainerDelta(path, this, prismContext);
    }

    /**
      * Shallow clone
      */
    @Override
    public PrismContainerDefinition<C> clone() {
        PrismContainerDefinition<C> clone = new PrismContainerDefinition<C>(name, complexTypeDefinition,
                prismContext, compileTimeClass);
        copyDefinitionData(clone);
        return clone;
    }

    protected void copyDefinitionData(PrismContainerDefinition<C> clone) {
        super.copyDefinitionData(clone);
        clone.complexTypeDefinition = this.complexTypeDefinition;
        clone.compileTimeClass = this.compileTimeClass;
    }

    @Override
    ItemDefinition deepClone(Map<QName, ComplexTypeDefinition> ctdMap) {
        PrismContainerDefinition<C> clone = clone();
        ComplexTypeDefinition ctd = getComplexTypeDefinition();
        if (ctd != null) {
            ctd = ctd.deepClone(ctdMap);
            clone.setComplexTypeDefinition(ctd);
        }
        return clone;
    }

    public PrismContainerDefinition<C> cloneWithReplacedDefinition(QName itemName, ItemDefinition newDefinition) {
        PrismContainerDefinition<C> clone = clone();
        ComplexTypeDefinition originalComplexTypeDefinition = getComplexTypeDefinition();
        ComplexTypeDefinition cloneComplexTypeDefinition = originalComplexTypeDefinition.clone();
        clone.setComplexTypeDefinition(cloneComplexTypeDefinition);
        cloneComplexTypeDefinition.replaceDefinition(itemName, newDefinition);
        return clone;
    }

    /**
     * Creates new instance of property definition and adds it to the container.
     * <p/>
     * This is the preferred method of creating a new definition.
     *
     * @param name     name of the property (element name)
     * @param typeName XSD type of the property
     * @return created property definition
     */
    public PrismPropertyDefinition createPropertyDefinition(QName name, QName typeName) {
        PrismPropertyDefinition propDef = new PrismPropertyDefinition(name, typeName, prismContext);
        addDefinition(propDef);
        return propDef;
    }

    private void addDefinition(ItemDefinition itemDef) {
        ((Collection) getDefinitions()).add(itemDef);
    }

    /**
     * Creates new instance of property definition and adds it to the container.
     * <p/>
     * This is the preferred method of creating a new definition.
     *
     * @param name      name of the property (element name)
     * @param typeName  XSD type of the property
     * @param minOccurs minimal number of occurrences
     * @param maxOccurs maximal number of occurrences (-1 means unbounded)
     * @return created property definition
     */
    public PrismPropertyDefinition createPropertyDefinition(QName name, QName typeName, int minOccurs,
            int maxOccurs) {
        PrismPropertyDefinition propDef = new PrismPropertyDefinition(name, typeName, prismContext);
        propDef.setMinOccurs(minOccurs);
        propDef.setMaxOccurs(maxOccurs);
        addDefinition(propDef);
        return propDef;
    }

    // Creates reference to other schema
    // TODO: maybe check if the name is in different namespace
    // TODO: maybe create entirely new concept of property reference?
    public PrismPropertyDefinition createPropertyDefinition(QName name) {
        PrismPropertyDefinition propDef = new PrismPropertyDefinition(name, null, prismContext);
        addDefinition(propDef);
        return propDef;
    }

    /**
     * Creates new instance of property definition and adds it to the container.
     * <p/>
     * This is the preferred method of creating a new definition.
     *
     * @param localName name of the property (element name) relative to the schema namespace
     * @param typeName  XSD type of the property
     * @return created property definition
     */
    public PrismPropertyDefinition createPropertyDefinition(String localName, QName typeName) {
        QName name = new QName(getSchemaNamespace(), localName);
        return createPropertyDefinition(name, typeName);
    }

    /**
     * Creates new instance of property definition and adds it to the container.
     * <p/>
     * This is the preferred method of creating a new definition.
     *
     * @param localName     name of the property (element name) relative to the schema namespace
     * @param localTypeName XSD type of the property
     * @return created property definition
     */
    public PrismPropertyDefinition createPropertyDefinition(String localName, String localTypeName) {
        QName name = new QName(getSchemaNamespace(), localName);
        QName typeName = new QName(getSchemaNamespace(), localTypeName);
        return createPropertyDefinition(name, typeName);
    }

    /**
     * Creates new instance of property definition and adds it to the container.
     * <p/>
     * This is the preferred method of creating a new definition.
     *
     * @param localName     name of the property (element name) relative to the schema namespace
     * @param localTypeName XSD type of the property
     * @param minOccurs     minimal number of occurrences
     * @param maxOccurs     maximal number of occurrences (-1 means unbounded)
     * @return created property definition
     */
    public PrismPropertyDefinition createPropertyDefinition(String localName, String localTypeName, int minOccurs,
            int maxOccurs) {
        QName name = new QName(getSchemaNamespace(), localName);
        QName typeName = new QName(getSchemaNamespace(), localTypeName);
        PrismPropertyDefinition propertyDefinition = createPropertyDefinition(name, typeName);
        propertyDefinition.setMinOccurs(minOccurs);
        propertyDefinition.setMaxOccurs(maxOccurs);
        return propertyDefinition;
    }

    public PrismContainerDefinition createContainerDefinition(QName name, QName typeName) {
        return createContainerDefinition(name, typeName, 1, 1);
    }

    public PrismContainerDefinition createContainerDefinition(QName name, QName typeName, int minOccurs,
            int maxOccurs) {
        PrismSchema typeSchema = prismContext.getSchemaRegistry().findSchemaByNamespace(typeName.getNamespaceURI());
        if (typeSchema == null) {
            throw new IllegalArgumentException(
                    "Schema for namespace " + typeName.getNamespaceURI() + " is not known in the prism context");
        }
        ComplexTypeDefinition typeDefinition = typeSchema.findComplexTypeDefinition(typeName);
        if (typeDefinition == null) {
            throw new IllegalArgumentException("Type " + typeName + " is not known in the schema");
        }
        return createContainerDefinition(name, typeDefinition, minOccurs, maxOccurs);
    }

    public PrismContainerDefinition<C> createContainerDefinition(QName name,
            ComplexTypeDefinition complexTypeDefinition, int minOccurs, int maxOccurs) {
        PrismContainerDefinition<C> def = new PrismContainerDefinition<C>(name, complexTypeDefinition,
                prismContext);
        def.setMinOccurs(minOccurs);
        def.setMaxOccurs(maxOccurs);
        addDefinition(def);
        return def;
    }

    public PrismContainerValue<C> createValue() {
        return new PrismContainerValue<C>(prismContext);
    }

    @Override
    public String debugDump(int indent) {
        StringBuilder sb = new StringBuilder();
        DebugUtil.indentDebugDump(sb, indent);
        sb.append(toString());
        if (isRuntimeSchema()) {
            sb.append(" dynamic");
        }
        for (Definition def : getDefinitions()) {
            sb.append("\n");
            if (def == this) {
                // Not perfect loop protection, but works for now
                DebugUtil.indentDebugDump(sb, indent);
                sb.append("<itself>");
            } else {
                sb.append(def.debugDump(indent + 1));
            }
        }
        return sb.toString();
    }

    public boolean isEmpty() {
        return complexTypeDefinition.isEmpty();
    }

    /**
     * Return a human readable name of this class suitable for logs.
     */
    @Override
    protected String getDebugDumpClassName() {
        return "PCD";
    }

    @Override
    public String getDocClassName() {
        return "container";
    }

}