Package org.apache.jackrabbit.core.nodetype

Source Code of org.apache.jackrabbit.core.nodetype.EffectiveNodeType

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.jackrabbit.core.nodetype;

import org.apache.jackrabbit.core.value.InternalValue;
import org.apache.jackrabbit.name.QName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;

/**
* An <code>EffectiveNodeType</code> represents one or more
* <code>NodeType</code>s as one 'effective' node type where inheritance
* is resolved.
* <p/>
* Instances of <code>EffectiveNodeType</code> are immutable.
*/
public class EffectiveNodeType implements Cloneable {
    private static Logger log = LoggerFactory.getLogger(EffectiveNodeType.class);

    // list of explicitly aggregated {i.e. merged) node types
    private final TreeSet mergedNodeTypes;
    // list of implicitly aggregated {through inheritance) node types
    private final TreeSet inheritedNodeTypes;
    // list of all either explicitly (through aggregation) or implicitly
    // (through inheritance) included node types.
    private final TreeSet allNodeTypes;
    // map of named item definitions (maps name to list of definitions)
    private final HashMap namedItemDefs;
    // list of unnamed item definitions (i.e. residual definitions)
    private final ArrayList unnamedItemDefs;

    /**
     * private constructor.
     */
    private EffectiveNodeType() {
        mergedNodeTypes = new TreeSet();
        inheritedNodeTypes = new TreeSet();
        allNodeTypes = new TreeSet();
        namedItemDefs = new HashMap();
        unnamedItemDefs = new ArrayList();
    }

    /**
     * Package private factory method.
     * <p/>
     * Creates an effective node type representation of a node type definition.
     * Note that the definitions of all referenced node types must be contained
     * in <code>ntdCache</code>.
     *
     * @param ntd      node type definition
     * @param entCache cache of already-built effective node types
     * @param ntdCache cache of node type definitions, used to resolve dependencies
     * @return an effective node type representation of the given node type definition.
     * @throws NodeTypeConflictException if the node type definition is invalid,
     *                                   e.g. due to ambiguous child definitions.
     * @throws NoSuchNodeTypeException if a node type reference (e.g. a supertype)
     *                                 could not be resolved.
     */
    static EffectiveNodeType create(NodeTypeDef ntd,
                                    EffectiveNodeTypeCache entCache,
                                    Map ntdCache)
            throws NodeTypeConflictException, NoSuchNodeTypeException {
        // create empty effective node type instance
        EffectiveNodeType ent = new EffectiveNodeType();
        QName ntName = ntd.getName();

        // prepare new instance
        ent.mergedNodeTypes.add(ntName);
        ent.allNodeTypes.add(ntName);

        // map of all item definitions (maps id to definition)
        // used to effectively detect ambiguous child definitions where
        // ambiguity is defined in terms of definition identity
        HashMap itemDefIds = new HashMap();

        NodeDef[] cnda = ntd.getChildNodeDefs();
        for (int i = 0; i < cnda.length; i++) {
            // check if child node definition would be ambiguous within
            // this node type definition
            if (itemDefIds.containsKey(cnda[i].getId())) {
                // conflict
                String msg;
                if (cnda[i].definesResidual()) {
                    msg = ntName + " contains ambiguous residual child node definitions";
                } else {
                    msg = ntName + " contains ambiguous definitions for child node named "
                            + cnda[i].getName();
                }
                log.debug(msg);
                throw new NodeTypeConflictException(msg);
            } else {
                itemDefIds.put(cnda[i].getId(), cnda[i]);
            }
            if (cnda[i].definesResidual()) {
                // residual node definition
                ent.unnamedItemDefs.add(cnda[i]);
            } else {
                // named node definition
                QName name = cnda[i].getName();
                List defs = (List) ent.namedItemDefs.get(name);
                if (defs == null) {
                    defs = new ArrayList();
                    ent.namedItemDefs.put(name, defs);
                }
                if (defs.size() > 0) {
                    /**
                     * there already exists at least one definition with that
                     * name; make sure none of them is auto-create
                     */
                    for (int j = 0; j < defs.size(); j++) {
                        ItemDef def = (ItemDef) defs.get(j);
                        if (cnda[i].isAutoCreated() || def.isAutoCreated()) {
                            // conflict
                            String msg = "There are more than one 'auto-create' item definitions for '"
                                    + name + "' in node type '" + ntName + "'";
                            log.debug(msg);
                            throw new NodeTypeConflictException(msg);
                        }
                    }
                }
                defs.add(cnda[i]);
            }
        }
        PropDef[] pda = ntd.getPropertyDefs();
        for (int i = 0; i < pda.length; i++) {
            // check if property definition would be ambiguous within
            // this node type definition
            if (itemDefIds.containsKey(pda[i].getId())) {
                // conflict
                String msg;
                if (pda[i].definesResidual()) {
                    msg = ntName + " contains ambiguous residual property definitions";
                } else {
                    msg = ntName + " contains ambiguous definitions for property named "
                            + pda[i].getName();
                }
                log.debug(msg);
                throw new NodeTypeConflictException(msg);
            } else {
                itemDefIds.put(pda[i].getId(), pda[i]);
            }
            if (pda[i].definesResidual()) {
                // residual property definition
                ent.unnamedItemDefs.add(pda[i]);
            } else {
                // named property definition
                QName name = pda[i].getName();
                List defs = (List) ent.namedItemDefs.get(name);
                if (defs == null) {
                    defs = new ArrayList();
                    ent.namedItemDefs.put(name, defs);
                }
                if (defs.size() > 0) {
                    /**
                     * there already exists at least one definition with that
                     * name; make sure none of them is auto-create
                     */
                    for (int j = 0; j < defs.size(); j++) {
                        ItemDef def = (ItemDef) defs.get(j);
                        if (pda[i].isAutoCreated() || def.isAutoCreated()) {
                            // conflict
                            String msg = "There are more than one 'auto-create' item definitions for '"
                                    + name + "' in node type '" + ntName + "'";
                            log.debug(msg);
                            throw new NodeTypeConflictException(msg);
                        }
                    }
                }
                defs.add(pda[i]);
            }
        }

        // resolve supertypes recursively
        QName[] supertypes = ntd.getSupertypes();
        if (supertypes.length > 0) {
            ent.internalMerge(NodeTypeRegistry.getEffectiveNodeType(supertypes, entCache, ntdCache), true);
        }

        // we're done
        return ent;
    }

    /**
     * Package private factory method for creating a new 'empty' effective
     * node type instance.
     *
     * @return an 'empty' effective node type instance.
     */
    static EffectiveNodeType create() {
        return new EffectiveNodeType();
    }

    public QName[] getMergedNodeTypes() {
        return (QName[]) mergedNodeTypes.toArray(new QName[mergedNodeTypes.size()]);
    }

    public QName[] getInheritedNodeTypes() {
        return (QName[]) inheritedNodeTypes.toArray(new QName[inheritedNodeTypes.size()]);
    }

    public QName[] getAllNodeTypes() {
        return (QName[]) allNodeTypes.toArray(new QName[allNodeTypes.size()]);
    }

    public ItemDef[] getAllItemDefs() {
        if (namedItemDefs.size() == 0 && unnamedItemDefs.size() == 0) {
            return ItemDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(namedItemDefs.size() + unnamedItemDefs.size());
        Iterator iter = namedItemDefs.values().iterator();
        while (iter.hasNext()) {
            defs.addAll((List) iter.next());
        }
        defs.addAll(unnamedItemDefs);
        if (defs.size() == 0) {
            return ItemDef.EMPTY_ARRAY;
        }
        return (ItemDef[]) defs.toArray(new ItemDef[defs.size()]);
    }

    public ItemDef[] getNamedItemDefs() {
        if (namedItemDefs.size() == 0) {
            return ItemDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(namedItemDefs.size());
        Iterator iter = namedItemDefs.values().iterator();
        while (iter.hasNext()) {
            defs.addAll((List) iter.next());
        }
        if (defs.size() == 0) {
            return ItemDef.EMPTY_ARRAY;
        }
        return (ItemDef[]) defs.toArray(new ItemDef[defs.size()]);
    }

    public ItemDef[] getUnnamedItemDefs() {
        if (unnamedItemDefs.size() == 0) {
            return ItemDef.EMPTY_ARRAY;
        }
        return (ItemDef[]) unnamedItemDefs.toArray(new ItemDef[unnamedItemDefs.size()]);
    }

    public boolean hasNamedItemDef(QName name) {
        return namedItemDefs.containsKey(name);
    }

    public ItemDef[] getNamedItemDefs(QName name) {
        List defs = (List) namedItemDefs.get(name);
        if (defs == null || defs.size() == 0) {
            return ItemDef.EMPTY_ARRAY;
        }
        return (ItemDef[]) defs.toArray(new ItemDef[defs.size()]);
    }

    public NodeDef[] getAllNodeDefs() {
        if (namedItemDefs.size() == 0 && unnamedItemDefs.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(namedItemDefs.size() + unnamedItemDefs.size());
        Iterator iter = unnamedItemDefs.iterator();
        while (iter.hasNext()) {
            ItemDef def = (ItemDef) iter.next();
            if (def.definesNode()) {
                defs.add(def);
            }
        }
        iter = namedItemDefs.values().iterator();
        while (iter.hasNext()) {
            List list = (List) iter.next();
            Iterator iter1 = list.iterator();
            while (iter1.hasNext()) {
                ItemDef def = (ItemDef) iter1.next();
                if (def.definesNode()) {
                    defs.add(def);
                }
            }
        }
        if (defs.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        return (NodeDef[]) defs.toArray(new NodeDef[defs.size()]);
    }

    public NodeDef[] getNamedNodeDefs() {
        if (namedItemDefs.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(namedItemDefs.size());
        Iterator iter = namedItemDefs.values().iterator();
        while (iter.hasNext()) {
            List list = (List) iter.next();
            Iterator iter1 = list.iterator();
            while (iter1.hasNext()) {
                ItemDef def = (ItemDef) iter1.next();
                if (def.definesNode()) {
                    defs.add(def);
                }
            }
        }
        if (defs.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        return (NodeDef[]) defs.toArray(new NodeDef[defs.size()]);
    }

    public NodeDef[] getNamedNodeDefs(QName name) {
        List list = (List) namedItemDefs.get(name);
        if (list == null || list.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(list.size());
        Iterator iter = list.iterator();
        while (iter.hasNext()) {
            ItemDef def = (ItemDef) iter.next();
            if (def.definesNode()) {
                defs.add(def);
            }
        }
        if (defs.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        return (NodeDef[]) defs.toArray(new NodeDef[defs.size()]);
    }

    public NodeDef[] getUnnamedNodeDefs() {
        if (unnamedItemDefs.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(unnamedItemDefs.size());
        Iterator iter = unnamedItemDefs.iterator();
        while (iter.hasNext()) {
            ItemDef def = (ItemDef) iter.next();
            if (def.definesNode()) {
                defs.add(def);
            }
        }
        if (defs.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        return (NodeDef[]) defs.toArray(new NodeDef[defs.size()]);
    }

    public NodeDef[] getAutoCreateNodeDefs() {
        // since auto-create items must have a name,
        // we're only searching the named item definitions
        if (namedItemDefs.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(namedItemDefs.size());
        Iterator iter = namedItemDefs.values().iterator();
        while (iter.hasNext()) {
            List list = (List) iter.next();
            Iterator iter1 = list.iterator();
            while (iter1.hasNext()) {
                ItemDef def = (ItemDef) iter1.next();
                if (def.definesNode() && def.isAutoCreated()) {
                    defs.add(def);
                }
            }
        }
        if (defs.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        return (NodeDef[]) defs.toArray(new NodeDef[defs.size()]);
    }

    public PropDef[] getAllPropDefs() {
        if (namedItemDefs.size() == 0 && unnamedItemDefs.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(namedItemDefs.size() + unnamedItemDefs.size());
        Iterator iter = unnamedItemDefs.iterator();
        while (iter.hasNext()) {
            ItemDef def = (ItemDef) iter.next();
            if (!def.definesNode()) {
                defs.add(def);
            }
        }
        iter = namedItemDefs.values().iterator();
        while (iter.hasNext()) {
            List list = (List) iter.next();
            Iterator iter1 = list.iterator();
            while (iter1.hasNext()) {
                ItemDef def = (ItemDef) iter1.next();
                if (!def.definesNode()) {
                    defs.add(def);
                }
            }
        }
        if (defs.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        return (PropDef[]) defs.toArray(new PropDef[defs.size()]);
    }

    public PropDef[] getNamedPropDefs() {
        if (namedItemDefs.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(namedItemDefs.size());
        Iterator iter = namedItemDefs.values().iterator();
        while (iter.hasNext()) {
            List list = (List) iter.next();
            Iterator iter1 = list.iterator();
            while (iter1.hasNext()) {
                ItemDef def = (ItemDef) iter1.next();
                if (!def.definesNode()) {
                    defs.add(def);
                }
            }
        }
        if (defs.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        return (PropDef[]) defs.toArray(new PropDef[defs.size()]);
    }

    public PropDef[] getNamedPropDefs(QName name) {
        List list = (List) namedItemDefs.get(name);
        if (list == null || list.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(list.size());
        Iterator iter = list.iterator();
        while (iter.hasNext()) {
            ItemDef def = (ItemDef) iter.next();
            if (!def.definesNode()) {
                defs.add(def);
            }
        }
        if (defs.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        return (PropDef[]) defs.toArray(new PropDef[defs.size()]);
    }

    public PropDef[] getUnnamedPropDefs() {
        if (unnamedItemDefs.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(unnamedItemDefs.size());
        Iterator iter = unnamedItemDefs.iterator();
        while (iter.hasNext()) {
            ItemDef def = (ItemDef) iter.next();
            if (!def.definesNode()) {
                defs.add(def);
            }
        }
        if (defs.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        return (PropDef[]) defs.toArray(new PropDef[defs.size()]);
    }

    public PropDef[] getAutoCreatePropDefs() {
        // since auto-create items must have a name,
        // we're only searching the named item definitions
        if (namedItemDefs.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(namedItemDefs.size());
        Iterator iter = namedItemDefs.values().iterator();
        while (iter.hasNext()) {
            List list = (List) iter.next();
            Iterator iter1 = list.iterator();
            while (iter1.hasNext()) {
                ItemDef def = (ItemDef) iter1.next();
                if (!def.definesNode() && def.isAutoCreated()) {
                    defs.add(def);
                }
            }
        }
        if (defs.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        return (PropDef[]) defs.toArray(new PropDef[defs.size()]);
    }

    public PropDef[] getMandatoryPropDefs() {
        // since mandatory items must have a name,
        // we're only searching the named item definitions
        if (namedItemDefs.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(namedItemDefs.size());
        Iterator iter = namedItemDefs.values().iterator();
        while (iter.hasNext()) {
            List list = (List) iter.next();
            Iterator iter1 = list.iterator();
            while (iter1.hasNext()) {
                ItemDef def = (ItemDef) iter1.next();
                if (!def.definesNode() && def.isMandatory()) {
                    defs.add(def);
                }
            }
        }
        if (defs.size() == 0) {
            return PropDef.EMPTY_ARRAY;
        }
        return (PropDef[]) defs.toArray(new PropDef[defs.size()]);
    }

    public NodeDef[] getMandatoryNodeDefs() {
        // since mandatory items must have a name,
        // we're only searching the named item definitions
        if (namedItemDefs.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        ArrayList defs = new ArrayList(namedItemDefs.size());
        Iterator iter = namedItemDefs.values().iterator();
        while (iter.hasNext()) {
            List list = (List) iter.next();
            Iterator iter1 = list.iterator();
            while (iter1.hasNext()) {
                ItemDef def = (ItemDef) iter1.next();
                if (def.definesNode() && def.isMandatory()) {
                    defs.add(def);
                }
            }
        }
        if (defs.size() == 0) {
            return NodeDef.EMPTY_ARRAY;
        }
        return (NodeDef[]) defs.toArray(new NodeDef[defs.size()]);
    }

    /**
     * Determines whether this effective node type representation includes
     * (either through inheritance or aggregation) the given node type.
     *
     * @param nodeTypeName name of node type
     * @return <code>true</code> if the given node type is included, otherwise
     *         <code>false</code>
     */
    public boolean includesNodeType(QName nodeTypeName) {
        return allNodeTypes.contains(nodeTypeName);
    }

    /**
     * Determines whether this effective node type representation includes
     * (either through inheritance or aggregation) all of the given node types.
     *
     * @param nodeTypeNames array of node type names
     * @return <code>true</code> if all of the given node types are included,
     *         otherwise <code>false</code>
     */
    public boolean includesNodeTypes(QName[] nodeTypeNames) {
        return allNodeTypes.containsAll(Arrays.asList(nodeTypeNames));
    }

    /**
     * Tests if the value constraints defined in the property definition
     * <code>pd</code> are satisfied by the the specified <code>values</code>.
     * <p/>
     * Note that the <i>protected</i> flag is not checked. Also note that no
     * type conversions are attempted if the type of the given values does not
     * match the required type as specified in the given definition.
     *
     * @param pd     The definiton of the property
     * @param values An array of <code>InternalValue</code> objects.
     * @throws ConstraintViolationException if the value constraints defined in
     *                                      the property definition are satisfied
     *                                      by the the specified values
     * @throws RepositoryException          if another error occurs
     */
    public static void checkSetPropertyValueConstraints(PropDef pd,
                                                        InternalValue[] values)
            throws ConstraintViolationException, RepositoryException {
        // check multi-value flag
        if (!pd.isMultiple() && values != null && values.length > 1) {
            throw new ConstraintViolationException("the property is not multi-valued");
        }

        ValueConstraint[] constraints = pd.getValueConstraints();
        if (constraints == null || constraints.length == 0) {
            // no constraints to check
            return;
        }
        if (values != null && values.length > 0) {
            // check value constraints on every value
            for (int i = 0; i < values.length; i++) {
                // constraints are OR-ed together
                boolean satisfied = false;
                ConstraintViolationException cve = null;
                for (int j = 0; j < constraints.length; j++) {
                    try {
                        constraints[j].check(values[i]);
                        satisfied = true;
                        break;
                    } catch (ConstraintViolationException e) {
                        cve = e;
                    }
                }
                if (!satisfied) {
                    // re-throw last exception we encountered
                    throw cve;
                }
            }
        }
    }

    /**
     * @param name
     * @throws ConstraintViolationException
     */
    public void checkAddNodeConstraints(QName name)
            throws ConstraintViolationException {
        try {
            getApplicableChildNodeDef(name, null, null);
        } catch (NoSuchNodeTypeException nsnte) {
            String msg = "internal eror: inconsistent node type";
            log.debug(msg);
            throw new ConstraintViolationException(msg, nsnte);
        }
    }

    /**
     * @param name
     * @param nodeTypeName
     * @param ntReg
     * @throws ConstraintViolationException
     * @throws NoSuchNodeTypeException
     */
    public void checkAddNodeConstraints(QName name, QName nodeTypeName,
                                        NodeTypeRegistry ntReg)
            throws ConstraintViolationException, NoSuchNodeTypeException {
        NodeDef nd = getApplicableChildNodeDef(name, nodeTypeName, ntReg);
        if (nd.isProtected()) {
            throw new ConstraintViolationException(name + " is protected");
        }
        if (nd.isAutoCreated()) {
            throw new ConstraintViolationException(name + " is auto-created and can not be manually added");
        }
    }

    /**
     * Returns the applicable child node definition for a child node with the
     * specified name and node type. If there are multiple applicable definitions
     * named definitions will take precedence over residual definitions.
     *
     * @param name
     * @param nodeTypeName
     * @param ntReg
     * @return
     * @throws NoSuchNodeTypeException
     * @throws ConstraintViolationException if no applicable child node definition
     *                                      could be found
     */
    public NodeDef getApplicableChildNodeDef(QName name, QName nodeTypeName,
                                             NodeTypeRegistry ntReg)
            throws NoSuchNodeTypeException, ConstraintViolationException {
        EffectiveNodeType entTarget;
        if (nodeTypeName != null) {
            entTarget = ntReg.getEffectiveNodeType(nodeTypeName);
        } else {
            entTarget = null;
        }

        // try named node definitions first
        ItemDef[] defs = getNamedItemDefs(name);
        if (defs != null) {
            for (int i = 0; i < defs.length; i++) {
                ItemDef def = defs[i];
                if (def.definesNode()) {
                    NodeDef nd = (NodeDef) def;
                    // node definition with that name exists
                    if (entTarget != null && nd.getRequiredPrimaryTypes() != null) {
                        // check 'required primary types' constraint
                        if (!entTarget.includesNodeTypes(nd.getRequiredPrimaryTypes())) {
                            continue;
                        }
                        // found named node definition
                        return nd;
                    } else {
                        if (nd.getDefaultPrimaryType() != null) {
                            // found node definition with default node type
                            return nd;
                        }
                    }
                }
            }
        }

        // no item with that name defined;
        // try residual node definitions
        NodeDef[] nda = getUnnamedNodeDefs();
        for (int i = 0; i < nda.length; i++) {
            NodeDef nd = nda[i];
            if (entTarget != null && nd.getRequiredPrimaryTypes() != null) {
                // check 'required primary types' constraint
                if (!entTarget.includesNodeTypes(nd.getRequiredPrimaryTypes())) {
                    continue;
                }
                // found residual node definition
                return nd;
            } else {
                // since no node type has been specified for the new node,
                // it must be determined from the default node type;
                if (nd.getDefaultPrimaryType() != null) {
                    // found residual node definition with default node type
                    return nd;
                }
            }
        }

        // no applicable definition found
        throw new ConstraintViolationException("no matching child node definition found for " + name);
    }

    /**
     * Returns the applicable property definition for a property with the
     * specified name, type and multiValued characteristic. If there are
     * multiple applicable definitions the following rules will be applied:
     * <ul>
     * <li>named definitions are preferred to residual definitions</li>
     * <li>definitions with specific required type are preferred to definitions
     * with required type UNDEFINED</li>
     * </ul>
     *
     * @param name
     * @param type
     * @param multiValued
     * @return
     * @throws ConstraintViolationException if no applicable property definition
     *                                      could be found
     */
    public PropDef getApplicablePropertyDef(QName name, int type,
                                            boolean multiValued)
            throws ConstraintViolationException {
        // try named property definitions first
        PropDef match =
                getMatchingPropDef(getNamedPropDefs(name), type, multiValued);
        if (match != null) {
            return match;
        }

        // no item with that name defined;
        // try residual property definitions
        match = getMatchingPropDef(getUnnamedPropDefs(), type, multiValued);
        if (match != null) {
            return match;
        }

        // no applicable definition found
        throw new ConstraintViolationException("no matching property definition found for " + name);
    }

    /**
     * Returns the applicable property definition for a property with the
     * specified name and type. The multiValued flag is not taken into account
     * in the selection algorithm. Other than
     * <code>{@link #getApplicablePropertyDef(QName, int, boolean)}</code>
     * this method does not take the multiValued flag into account in the
     * selection algorithm. If there more than one applicable definitions then
     * the following rules are applied:
     * <ul>
     * <li>named definitions are preferred to residual definitions</li>
     * <li>definitions with specific required type are preferred to definitions
     * with required type UNDEFINED</li>
     * <li>single-value definitions are preferred to multiple-value definitions</li>
     * </ul>
     *
     * @param name
     * @param type
     * @return
     * @throws ConstraintViolationException if no applicable property definition
     *                                      could be found
     */
    public PropDef getApplicablePropertyDef(QName name, int type)
            throws ConstraintViolationException {
        // try named property definitions first
        PropDef match = getMatchingPropDef(getNamedPropDefs(name), type);
        if (match != null) {
            return match;
        }

        // no item with that name defined;
        // try residual property definitions
        match = getMatchingPropDef(getUnnamedPropDefs(), type);
        if (match != null) {
            return match;
        }

        // no applicable definition found
        throw new ConstraintViolationException("no matching property definition found for " + name);
    }

    private PropDef getMatchingPropDef(PropDef[] defs, int type) {
        PropDef match = null;
        for (int i = 0; i < defs.length; i++) {
            ItemDef def = defs[i];
            if (!def.definesNode()) {
                PropDef pd = (PropDef) def;
                int reqType = pd.getRequiredType();
                // match type
                if (reqType == PropertyType.UNDEFINED
                        || type == PropertyType.UNDEFINED
                        || reqType == type) {
                    if (match == null) {
                        match = pd;
                    } else {
                        // check if this definition is a better match than
                        // the one we've already got
                        if (match.getRequiredType() != pd.getRequiredType()) {
                            if (match.getRequiredType() == PropertyType.UNDEFINED) {
                                // found better match
                                match = pd;
                            }
                        } else {
                            if (match.isMultiple() && !pd.isMultiple()) {
                                // found better match
                                match = pd;
                            }
                        }
                    }
                    if (match.getRequiredType() != PropertyType.UNDEFINED
                            && !match.isMultiple()) {
                        // found best possible match, get outta here
                        return match;
                    }
                }
            }
        }
        return match;
    }

    private PropDef getMatchingPropDef(PropDef[] defs, int type,
                                       boolean multiValued) {
        PropDef match = null;
        for (int i = 0; i < defs.length; i++) {
            ItemDef def = defs[i];
            if (!def.definesNode()) {
                PropDef pd = (PropDef) def;
                int reqType = pd.getRequiredType();
                // match type
                if (reqType == PropertyType.UNDEFINED
                        || type == PropertyType.UNDEFINED
                        || reqType == type) {
                    // match multiValued flag
                    if (multiValued == pd.isMultiple()) {
                        // found match
                        if (pd.getRequiredType() != PropertyType.UNDEFINED) {
                            // found best possible match, get outta here
                            return pd;
                        } else {
                            if (match == null) {
                                match = pd;
                            }
                        }
                    }
                }
            }
        }
        return match;
    }

    /**
     * @param name
     * @throws ConstraintViolationException
     */
    public void checkRemoveItemConstraints(QName name) throws ConstraintViolationException {
        /**
         * as there might be multiple definitions with the same name and we
         * don't know which one is applicable, we check all of them
         */
        ItemDef[] defs = getNamedItemDefs(name);
        if (defs != null) {
            for (int i = 0; i < defs.length; i++) {
                if (defs[i].isMandatory()) {
                    throw new ConstraintViolationException("can't remove mandatory item");
                }
                if (defs[i].isProtected()) {
                    throw new ConstraintViolationException("can't remove protected item");
                }
            }
        }
    }

    /**
     * Merges another <code>EffectiveNodeType</code> with this one.
     * Checks for merge conflicts.
     *
     * @param other
     * @return
     * @throws NodeTypeConflictException
     */
    EffectiveNodeType merge(EffectiveNodeType other)
            throws NodeTypeConflictException {
        // create a clone of this instance and perform the merge on
        // the 'clone' to avoid a potentially inconsistant state
        // of this instance if an exception is thrown during
        // the merge.
        EffectiveNodeType copy = (EffectiveNodeType) clone();
        copy.internalMerge(other, false);
        return copy;
    }

    /**
     * Internal helper method which merges another <code>EffectiveNodeType</code>
     * instance with <i>this</i> instance.
     * <p/>
     * Warning: This instance might be in an inconsistent state if an exception
     * is thrown.
     *
     * @param other
     * @param supertype true if the merge is a result of inheritance, i.e. <code>other</code>
     *                  represents one or more supertypes of this instance; otherwise false, i.e.
     *                  the merge is the result of an explicit aggregation
     * @throws NodeTypeConflictException
     */
    private synchronized void internalMerge(EffectiveNodeType other, boolean supertype)
            throws NodeTypeConflictException {
        QName[] nta = other.getAllNodeTypes();
        int includedCount = 0;
        for (int i = 0; i < nta.length; i++) {
            if (includesNodeType(nta[i])) {
                // redundant node type
                log.debug("node type '" + nta[i] + "' is already contained.");
                includedCount++;
            }
        }
        if (includedCount == nta.length) {
            // total overlap, ignore
            return;
        }

        // named item definitions
        ItemDef[] defs = other.getNamedItemDefs();
        for (int i = 0; i < defs.length; i++) {
            ItemDef def = defs[i];
            if (includesNodeType(def.getDeclaringNodeType())) {
                // ignore redundant definitions
                continue;
            }
            QName name = def.getName();
            List existingDefs = (List) namedItemDefs.get(name);
            if (existingDefs != null) {
                if (existingDefs.size() > 0) {
                    // there already exists at least one definition with that name
                    for (int j = 0; j < existingDefs.size(); j++) {
                        ItemDef existingDef = (ItemDef) existingDefs.get(j);
                        // make sure none of them is auto-create
                        if (def.isAutoCreated() || existingDef.isAutoCreated()) {
                            // conflict
                            String msg = "The item definition for '" + name
                                    + "' in node type '"
                                    + def.getDeclaringNodeType()
                                    + "' conflicts with node type '"
                                    + existingDef.getDeclaringNodeType()
                                    + "': name collision with auto-create definition";
                            log.debug(msg);
                            throw new NodeTypeConflictException(msg);
                        }
                        // check ambiguous definitions
                        if (def.definesNode() == existingDef.definesNode()) {
                            if (!def.definesNode()) {
                                // property definition
                                PropDef pd = (PropDef) def;
                                PropDef epd = (PropDef) existingDef;
                                // compare type & multiValued flag
                                if (pd.getRequiredType() == epd.getRequiredType()
                                        && pd.isMultiple() == epd.isMultiple()) {
                                    // conflict
                                    String msg = "The property definition for '"
                                            + name + "' in node type '"
                                            + def.getDeclaringNodeType()
                                            + "' conflicts with node type '"
                                            + existingDef.getDeclaringNodeType()
                                            + "': ambiguous property definition";
                                    log.debug(msg);
                                    throw new NodeTypeConflictException(msg);
                                }
                            } else {
                                // child node definition
                                // conflict
                                String msg = "The child node definition for '"
                                        + name + "' in node type '"
                                        + def.getDeclaringNodeType()
                                        + "' conflicts with node type '"
                                        + existingDef.getDeclaringNodeType()
                                        + "': ambiguous child node definition";
                                log.debug(msg);
                                throw new NodeTypeConflictException(msg);
                            }
                        }
                    }
                }
            } else {
                existingDefs = new ArrayList();
                namedItemDefs.put(name, existingDefs);
            }
            existingDefs.add(def);
        }

        // residual item definitions
        defs = other.getUnnamedItemDefs();
        for (int i = 0; i < defs.length; i++) {
            ItemDef def = defs[i];
            if (includesNodeType(def.getDeclaringNodeType())) {
                // ignore redundant definitions
                continue;
            }
            Iterator iter = unnamedItemDefs.iterator();
            while (iter.hasNext()) {
                ItemDef existing = (ItemDef) iter.next();
                // compare with existing definition
                if (def.definesNode() == existing.definesNode()) {
                    if (!def.definesNode()) {
                        // property definition
                        PropDef pd = (PropDef) def;
                        PropDef epd = (PropDef) existing;
                        // compare type & multiValued flag
                        if (pd.getRequiredType() == epd.getRequiredType()
                                && pd.isMultiple() == epd.isMultiple()) {
                            // conflict
                            String msg = "A property definition in node type '"
                                    + def.getDeclaringNodeType()
                                    + "' conflicts with node type '"
                                    + existing.getDeclaringNodeType()
                                    + "': ambiguous residual property definition";
                            log.debug(msg);
                            throw new NodeTypeConflictException(msg);
                        }
                    } else {
                        // child node definition
                        NodeDef nd = (NodeDef) def;
                        NodeDef end = (NodeDef) existing;
                        // compare required & default primary types
                        if (Arrays.equals(nd.getRequiredPrimaryTypes(), end.getRequiredPrimaryTypes())
                                && (nd.getDefaultPrimaryType() == null
                                ? end.getDefaultPrimaryType() == null
                                : nd.getDefaultPrimaryType().equals(end.getDefaultPrimaryType()))) {
                            // conflict
                            String msg = "A child node definition in node type '"
                                    + def.getDeclaringNodeType()
                                    + "' conflicts with node type '"
                                    + existing.getDeclaringNodeType()
                                    + "': ambiguous residual child node definition";
                            log.debug(msg);
                            throw new NodeTypeConflictException(msg);
                        }
                    }
                }
            }
            unnamedItemDefs.add(def);
        }
        for (int i = 0; i < nta.length; i++) {
            allNodeTypes.add(nta[i]);
        }

        if (supertype) {
            // implicit merge as result of inheritance

            // add other merged node types as supertypes
            nta = other.getMergedNodeTypes();
            for (int i = 0; i < nta.length; i++) {
                inheritedNodeTypes.add(nta[i]);
            }
            // add supertypes of other merged node types as supertypes
            nta = other.getInheritedNodeTypes();
            for (int i = 0; i < nta.length; i++) {
                inheritedNodeTypes.add(nta[i]);
            }
        } else {
            // explicit merge

            // merge with other merged node types
            nta = other.getMergedNodeTypes();
            for (int i = 0; i < nta.length; i++) {
                mergedNodeTypes.add(nta[i]);
            }
            // add supertypes of other merged node types as supertypes
            nta = other.getInheritedNodeTypes();
            for (int i = 0; i < nta.length; i++) {
                inheritedNodeTypes.add(nta[i]);
            }
        }
    }

    protected Object clone() {
        EffectiveNodeType clone = new EffectiveNodeType();

        clone.mergedNodeTypes.addAll(mergedNodeTypes);
        clone.inheritedNodeTypes.addAll(inheritedNodeTypes);
        clone.allNodeTypes.addAll(allNodeTypes);
        Iterator iter = namedItemDefs.keySet().iterator();
        while (iter.hasNext()) {
            Object key = iter.next();
            List list = (List) namedItemDefs.get(key);
            clone.namedItemDefs.put(key, new ArrayList(list));
        }
        clone.unnamedItemDefs.addAll(unnamedItemDefs);

        return clone;
    }
}
TOP

Related Classes of org.apache.jackrabbit.core.nodetype.EffectiveNodeType

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.