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

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2010-2016 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 com.evolveum.midpoint.util.exception.SchemaException;

import javax.xml.namespace.QName;

import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;

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 PrismContainerDefinitionImpl<C extends Containerable> extends ItemDefinitionImpl<PrismContainer<C>>
        implements PrismContainerDefinition<C> {

    private static final long serialVersionUID = -5068923696147960699L;

    // There are situations where CTD is (maybe) null but class is defined.
    // TODO clean up this.
    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 PrismContainerDefinitionImpl(@NotNull QName name, ComplexTypeDefinition complexTypeDefinition,
            @NotNull PrismContext prismContext) {
        this(name, complexTypeDefinition, prismContext, null);
    }

    public PrismContainerDefinitionImpl(@NotNull QName name, ComplexTypeDefinition complexTypeDefinition,
            @NotNull 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();
    }

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

    @Override
    public ComplexTypeDefinition getComplexTypeDefinition() {
        return complexTypeDefinition;
    }

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

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

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

    @Override
    public <D extends ItemDefinition> D findItemDefinition(@NotNull QName name, @NotNull Class<D> clazz,
            boolean caseInsensitive) {
        if (complexTypeDefinition != null) {
            return complexTypeDefinition.findItemDefinition(name, clazz, caseInsensitive);
        } else {
            return null; // xsd:any and similar dynamic definitions
        }
    }

    @Override
    public String getDefaultNamespace() {
        return complexTypeDefinition != null ? complexTypeDefinition.getDefaultNamespace() : null;
    }

    @Override
    public List<String> getIgnoredNamespaces() {
        return complexTypeDefinition != null ? complexTypeDefinition.getIgnoredNamespaces() : null;
    }

    public <ID extends ItemDefinition> ID findItemDefinition(@NotNull ItemPath path, @NotNull 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) {
                ItemPath rest = path.rest();
                ComplexTypeDefinition parent = getSchemaRegistry()
                        .determineParentDefinition(getComplexTypeDefinition(), rest);
                if (rest.isEmpty()) {
                    // requires that the parent is defined as an item (container, object)
                    return (ID) getSchemaRegistry().findItemDefinitionByType(parent.getTypeName());
                } else {
                    return parent.findItemDefinition(rest, 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);
            }
        }
    }

    @Override
    public <ID extends ItemDefinition> ID findNamedItemDefinition(@NotNull QName firstName, @NotNull ItemPath rest,
            @NotNull 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;
    }

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

    /**
     * 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
     */
    @Override
    public List<? extends ItemDefinition> getDefinitions() {
        if (complexTypeDefinition == null) {
            // e.g. for xsd:any containers
            // FIXME
            return new ArrayList<>();
        }
        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
     */
    @Override
    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;
    }

    @NotNull
    @Override
    public PrismContainer<C> instantiate() throws SchemaException {
        return instantiate(getName());
    }

    @NotNull
    @Override
    public PrismContainer<C> instantiate(QName elementName) throws SchemaException {
        if (isAbstract()) {
            throw new SchemaException("Cannot instantiate abstract definition " + this);
        }
        elementName = addNamespaceIfApplicable(elementName);
        return new PrismContainer<>(elementName, this, prismContext);
    }

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

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

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

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

    @Override
    public PrismContainerDefinition<C> cloneWithReplacedDefinition(QName itemName, ItemDefinition newDefinition) {
        PrismContainerDefinitionImpl<C> clone = clone();
        clone.replaceDefinition(itemName, newDefinition);
        return clone;
    }

    @Override
    public void replaceDefinition(QName itemName, ItemDefinition newDefinition) {
        ComplexTypeDefinition originalComplexTypeDefinition = getComplexTypeDefinition();
        ComplexTypeDefinition cloneComplexTypeDefinition = originalComplexTypeDefinition.clone();
        setComplexTypeDefinition(cloneComplexTypeDefinition);
        ((ComplexTypeDefinitionImpl) cloneComplexTypeDefinition).replaceDefinition(itemName, newDefinition);
    }

    /**
     * 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 PrismPropertyDefinitionImpl createPropertyDefinition(QName name, QName typeName) {
        PrismPropertyDefinitionImpl propDef = new PrismPropertyDefinitionImpl(name, typeName, prismContext);
        addDefinition(propDef);
        return propDef;
    }

    private void addDefinition(ItemDefinition itemDef) {
        if (complexTypeDefinition == null) {
            throw new UnsupportedOperationException(
                    "Cannot add an item definition because there's no complex type definition");
        } else if (!(complexTypeDefinition instanceof ComplexTypeDefinitionImpl)) {
            throw new UnsupportedOperationException(
                    "Cannot add an item definition into complex type definition of type "
                            + complexTypeDefinition.getClass().getName());
        } else {
            ((ComplexTypeDefinitionImpl) complexTypeDefinition).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) {
        PrismPropertyDefinitionImpl propDef = new PrismPropertyDefinitionImpl(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 PrismPropertyDefinitionImpl(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);
        PrismPropertyDefinitionImpl 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) {
        PrismContainerDefinitionImpl<C> def = new PrismContainerDefinitionImpl<C>(name, complexTypeDefinition,
                prismContext);
        def.setMinOccurs(minOccurs);
        def.setMaxOccurs(maxOccurs);
        addDefinition(def);
        return def;
    }

    @Override
    public PrismContainerValue<C> createValue() {
        return new PrismContainerValue<>(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();
    }

    @Override
    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";
    }

}