/* * 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.jcr2spi.nodetype; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.NoSuchNodeTypeException; import org.apache.jackrabbit.spi.Name; import org.apache.jackrabbit.spi.QItemDefinition; import org.apache.jackrabbit.spi.QNodeDefinition; import org.apache.jackrabbit.spi.QNodeTypeDefinition; import org.apache.jackrabbit.spi.QPropertyDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 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 EffectiveNodeTypeImpl implements Cloneable, EffectiveNodeType { private static Logger log = LoggerFactory.getLogger(EffectiveNodeTypeImpl.class); // list of explicitly aggregated {i.e. merged) node types private final TreeSet<Name> mergedNodeTypes = new TreeSet<Name>(); // list of implicitly aggregated {through inheritance) node types private final TreeSet<Name> inheritedNodeTypes = new TreeSet<Name>(); // list of all either explicitly (through aggregation) or implicitly // (through inheritance) included node types. private final TreeSet<Name> allNodeTypes = new TreeSet<Name>(); // map of named item definitions (maps name to list of definitions) private final Map<Name, List<QItemDefinition>> namedItemDefs = new HashMap<Name, List<QItemDefinition>>(); // list of unnamed item definitions (i.e. residual definitions) private final List<QItemDefinition> unnamedItemDefs = new ArrayList<QItemDefinition>(); // (optional) set of additional mixins supported on node type private Set<Name> supportedMixins; /** * constructor. */ EffectiveNodeTypeImpl(TreeSet<Name> mergedNodeTypes, TreeSet<Name> inheritedNodeTypes, TreeSet<Name> allNodeTypes, Map<Name, List<QItemDefinition>> namedItemDefs, List<QItemDefinition> unnamedItemDefs, Set<Name> supportedMixins) { this.mergedNodeTypes.addAll(mergedNodeTypes); this.inheritedNodeTypes.addAll(inheritedNodeTypes); this.allNodeTypes.addAll(allNodeTypes); for (Map.Entry<Name, List<QItemDefinition>> entry : namedItemDefs.entrySet()) { this.namedItemDefs.put(entry.getKey(), new ArrayList<QItemDefinition>(entry.getValue())); } this.unnamedItemDefs.addAll(unnamedItemDefs); if (supportedMixins != null) { this.supportedMixins = new HashSet<Name>(); this.supportedMixins.addAll(supportedMixins); } } //--------------------------------------------------< EffectiveNodeType >--- /** * @see EffectiveNodeType#getInheritedNodeTypes() */ public Name[] getInheritedNodeTypes() { return inheritedNodeTypes.toArray(new Name[inheritedNodeTypes.size()]); } /** * @see EffectiveNodeType#getAllNodeTypes() */ public Name[] getAllNodeTypes() { return allNodeTypes.toArray(new Name[allNodeTypes.size()]); } /** * @see EffectiveNodeType#getMergedNodeTypes() */ public Name[] getMergedNodeTypes() { return mergedNodeTypes.toArray(new Name[mergedNodeTypes.size()]); } /** * @see EffectiveNodeType#getAllQNodeDefinitions() */ public QNodeDefinition[] getAllQNodeDefinitions() { if (namedItemDefs.size() == 0 && unnamedItemDefs.size() == 0) { return QNodeDefinition.EMPTY_ARRAY; } ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size() + unnamedItemDefs.size()); for (QItemDefinition qDef : unnamedItemDefs) { if (qDef.definesNode()) { defs.add(qDef); } } for (List<QItemDefinition> list : namedItemDefs.values()) { for (QItemDefinition qDef : list) { if (qDef.definesNode()) { defs.add(qDef); } } } if (defs.size() == 0) { return QNodeDefinition.EMPTY_ARRAY; } return defs.toArray(new QNodeDefinition[defs.size()]); } /** * @see EffectiveNodeType#getAllQPropertyDefinitions() */ public QPropertyDefinition[] getAllQPropertyDefinitions() { if (namedItemDefs.size() == 0 && unnamedItemDefs.size() == 0) { return QPropertyDefinition.EMPTY_ARRAY; } ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size() + unnamedItemDefs.size()); for (QItemDefinition qDef : unnamedItemDefs) { if (!qDef.definesNode()) { defs.add(qDef); } } for (List<QItemDefinition> list : namedItemDefs.values()) { for (QItemDefinition qDef : list) { if (!qDef.definesNode()) { defs.add(qDef); } } } if (defs.size() == 0) { return QPropertyDefinition.EMPTY_ARRAY; } return defs.toArray(new QPropertyDefinition[defs.size()]); } /** * @see EffectiveNodeType#getAutoCreateQNodeDefinitions() */ public QNodeDefinition[] getAutoCreateQNodeDefinitions() { // since auto-create items must have a name, // we're only searching the named item definitions if (namedItemDefs.size() == 0) { return QNodeDefinition.EMPTY_ARRAY; } ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size()); for (List<QItemDefinition> list : namedItemDefs.values()) { for (QItemDefinition qDef : list) { if (qDef.definesNode() && qDef.isAutoCreated()) { defs.add(qDef); } } } if (defs.size() == 0) { return QNodeDefinition.EMPTY_ARRAY; } return defs.toArray(new QNodeDefinition[defs.size()]); } /** * @see EffectiveNodeType#getAutoCreateQPropertyDefinitions() */ public QPropertyDefinition[] getAutoCreateQPropertyDefinitions() { // since auto-create items must have a name, // we're only searching the named item definitions if (namedItemDefs.size() == 0) { return QPropertyDefinition.EMPTY_ARRAY; } ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size()); for (List<QItemDefinition> list : namedItemDefs.values()) { for (QItemDefinition qDef : list) { if (!qDef.definesNode() && qDef.isAutoCreated()) { defs.add(qDef); } } } if (defs.size() == 0) { return QPropertyDefinition.EMPTY_ARRAY; } return defs.toArray(new QPropertyDefinition[defs.size()]); } /** * @see EffectiveNodeType#getMandatoryQPropertyDefinitions() */ public QPropertyDefinition[] getMandatoryQPropertyDefinitions() { // since mandatory items must have a name, // we're only searching the named item definitions if (namedItemDefs.size() == 0) { return QPropertyDefinition.EMPTY_ARRAY; } ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size()); for (List<QItemDefinition> list : namedItemDefs.values()) { for (QItemDefinition qDef : list) { if (!qDef.definesNode() && qDef.isMandatory()) { defs.add(qDef); } } } if (defs.size() == 0) { return QPropertyDefinition.EMPTY_ARRAY; } return defs.toArray(new QPropertyDefinition[defs.size()]); } /** * @see EffectiveNodeType#getMandatoryQNodeDefinitions() */ public QNodeDefinition[] getMandatoryQNodeDefinitions() { // since mandatory items must have a name, // we're only searching the named item definitions if (namedItemDefs.size() == 0) { return QNodeDefinition.EMPTY_ARRAY; } ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size()); for (List<QItemDefinition> list : namedItemDefs.values()) { for (QItemDefinition qDef : list) { if (qDef.definesNode() && qDef.isMandatory()) { defs.add(qDef); } } } if (defs.size() == 0) { return QNodeDefinition.EMPTY_ARRAY; } return defs.toArray(new QNodeDefinition[defs.size()]); } /** * @see EffectiveNodeType#getNamedQNodeDefinitions(Name) */ public QNodeDefinition[] getNamedQNodeDefinitions(Name name) { List<QItemDefinition> list = namedItemDefs.get(name); if (list == null || list.size() == 0) { return QNodeDefinition.EMPTY_ARRAY; } ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(list.size()); for (QItemDefinition qDef : list) { if (qDef.definesNode()) { defs.add(qDef); } } if (defs.size() == 0) { return QNodeDefinition.EMPTY_ARRAY; } return defs.toArray(new QNodeDefinition[defs.size()]); } /** * @see EffectiveNodeType#getUnnamedQNodeDefinitions() */ public QNodeDefinition[] getUnnamedQNodeDefinitions() { if (unnamedItemDefs.size() == 0) { return QNodeDefinition.EMPTY_ARRAY; } ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(unnamedItemDefs.size()); for (QItemDefinition qDef : unnamedItemDefs) { if (qDef.definesNode()) { defs.add(qDef); } } if (defs.size() == 0) { return QNodeDefinition.EMPTY_ARRAY; } return defs.toArray(new QNodeDefinition[defs.size()]); } /** * @see EffectiveNodeType#getNamedQPropertyDefinitions(Name) */ public QPropertyDefinition[] getNamedQPropertyDefinitions(Name name) { List<QItemDefinition> list = namedItemDefs.get(name); if (list == null || list.size() == 0) { return QPropertyDefinition.EMPTY_ARRAY; } ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(list.size()); for (QItemDefinition qDef : list) { if (!qDef.definesNode()) { defs.add(qDef); } } if (defs.size() == 0) { return QPropertyDefinition.EMPTY_ARRAY; } return defs.toArray(new QPropertyDefinition[defs.size()]); } /** * @see EffectiveNodeType#getUnnamedQPropertyDefinitions() */ public QPropertyDefinition[] getUnnamedQPropertyDefinitions() { if (unnamedItemDefs.size() == 0) { return QPropertyDefinition.EMPTY_ARRAY; } ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(unnamedItemDefs.size()); for (QItemDefinition qDef : unnamedItemDefs) { if (!qDef.definesNode()) { defs.add(qDef); } } if (defs.size() == 0) { return QPropertyDefinition.EMPTY_ARRAY; } return defs.toArray(new QPropertyDefinition[defs.size()]); } public boolean includesNodeType(Name nodeTypeName) { return allNodeTypes.contains(nodeTypeName); } public boolean includesNodeTypes(Name[] nodeTypeNames) { return allNodeTypes.containsAll(Arrays.asList(nodeTypeNames)); } /** * @see EffectiveNodeType#supportsMixin(Name) */ public boolean supportsMixin(Name mixin) { if (supportedMixins == null) { return true; } else { return supportedMixins.contains(mixin); } } /** * @see EffectiveNodeType#checkAddNodeConstraints(Name, ItemDefinitionProvider) */ public void checkAddNodeConstraints(Name name, ItemDefinitionProvider definitionProvider) throws ConstraintViolationException { try { definitionProvider.getQNodeDefinition(this, name, null); } catch (NoSuchNodeTypeException e) { String msg = "internal error: inconsistent node type"; log.debug(msg); throw new ConstraintViolationException(msg, e); } } /** * @see EffectiveNodeType#checkAddNodeConstraints(org.apache.jackrabbit.spi.Name,QNodeTypeDefinition, ItemDefinitionProvider) */ public void checkAddNodeConstraints(Name name, QNodeTypeDefinition nodeTypeDefinition, ItemDefinitionProvider definitionProvider) throws ConstraintViolationException, NoSuchNodeTypeException { if (nodeTypeDefinition.isAbstract()) { throw new ConstraintViolationException("not allowed to add node " + name + ": " + nodeTypeDefinition.getName() + " is abstract and cannot be used as primary node type."); } if (nodeTypeDefinition.isMixin()) { throw new ConstraintViolationException("not allowed to add node " + name + ":" + nodeTypeDefinition.getName() + " is a mixin and cannot be used as primary node type."); } QNodeDefinition nd = definitionProvider.getQNodeDefinition(this, name, nodeTypeDefinition.getName()); 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"); } } /** * @see EffectiveNodeType#checkRemoveItemConstraints(Name) */ public void checkRemoveItemConstraints(Name 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 */ QItemDefinition[] defs = getNamedItemDefs(name); if (hasRemoveConstraint(defs)) { throw new ConstraintViolationException("can't remove mandatory or protected item"); } } /** * @see EffectiveNodeType#hasRemoveNodeConstraint(Name) */ public boolean hasRemoveNodeConstraint(Name nodeName) { QNodeDefinition[] defs = getNamedQNodeDefinitions(nodeName); return hasRemoveConstraint(defs); } /** * @see EffectiveNodeType#hasRemovePropertyConstraint(Name) */ public boolean hasRemovePropertyConstraint(Name propertyName) { QPropertyDefinition[] defs = getNamedQPropertyDefinitions(propertyName); return hasRemoveConstraint(defs); } //---------------------------------------------< impl. specific methods >--- /** * Loop over the specified definitions and return <code>true</code> as soon * as the first mandatory or protected definition is encountered. * * @param defs * @return <code>true</code> if a mandatory or protected definition is present. */ private static boolean hasRemoveConstraint(QItemDefinition[] defs) { /** * as there might be multiple definitions with the same name that may be * applicable, return true as soon as the first mandatory or protected * definition is encountered. */ if (defs != null) { for (int i = 0; i < defs.length; i++) { if (defs[i].isMandatory()) { return true; } if (defs[i].isProtected()) { return true; } } } return false; } private QItemDefinition[] getNamedItemDefs() { if (namedItemDefs.size() == 0) { return QItemDefinition.EMPTY_ARRAY; } ArrayList<QItemDefinition> defs = new ArrayList<QItemDefinition>(namedItemDefs.size()); for (List<QItemDefinition> list : namedItemDefs.values()) { defs.addAll(list); } if (defs.size() == 0) { return QItemDefinition.EMPTY_ARRAY; } return defs.toArray(new QItemDefinition[defs.size()]); } private QItemDefinition[] getNamedItemDefs(Name name) { List<QItemDefinition> list = namedItemDefs.get(name); if (list == null || list.size() == 0) { return QNodeDefinition.EMPTY_ARRAY; } return list.toArray(new QItemDefinition[list.size()]); } private QItemDefinition[] getUnnamedItemDefs() { if (unnamedItemDefs.size() == 0) { return QItemDefinition.EMPTY_ARRAY; } return unnamedItemDefs.toArray(new QItemDefinition[unnamedItemDefs.size()]); } /** * Merges another <code>EffectiveNodeType</code> with this one. * Checks for merge conflicts. * * @param other * @return * @throws ConstraintViolationException */ EffectiveNodeTypeImpl merge(EffectiveNodeTypeImpl other) throws ConstraintViolationException { // create a clone of this instance and perform the merge on // the 'clone' to avoid a potentially inconsistent state // of this instance if an exception is thrown during // the merge. EffectiveNodeTypeImpl copy = (EffectiveNodeTypeImpl) 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 ConstraintViolationException */ synchronized void internalMerge(EffectiveNodeTypeImpl other, boolean supertype) throws ConstraintViolationException { Name[] 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 QItemDefinition[] defs = other.getNamedItemDefs(); for (int i = 0; i < defs.length; i++) { QItemDefinition qDef = defs[i]; if (includesNodeType(qDef.getDeclaringNodeType())) { // ignore redundant definitions continue; } Name name = qDef.getName(); List<QItemDefinition> existingDefs = 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++) { QItemDefinition qItemDef = existingDefs.get(j); // make sure none of them is auto-create if (qDef.isAutoCreated() || qItemDef.isAutoCreated()) { // conflict String msg = "The item definition for '" + name + "' in node type '" + qDef.getDeclaringNodeType() + "' conflicts with the one of node type '" + qItemDef.getDeclaringNodeType() + "': name collision with auto-create definition"; log.debug(msg); throw new ConstraintViolationException(msg); } // check ambiguous definitions if (qDef.definesNode() == qItemDef.definesNode()) { if (!qDef.definesNode()) { // property definition QPropertyDefinition pd = (QPropertyDefinition) qDef; QPropertyDefinition epd = (QPropertyDefinition) qItemDef; // compare type & multiValued flag if (pd.getRequiredType() == epd.getRequiredType() && pd.isMultiple() == epd.isMultiple()) { // conflict String msg = "The property definition for '" + name + "' in node type '" + qDef.getDeclaringNodeType() + "' conflicts with the one of node type '" + qItemDef.getDeclaringNodeType() + "': ambiguous property definition. " + "they must differ in required type " + "or cardinality."; log.debug(msg); throw new ConstraintViolationException(msg); } } else { // child node definition // conflict String msg = "The child node definition for '" + name + "' in node type '" + qDef.getDeclaringNodeType() + "' conflicts with the one of node type '" + qItemDef.getDeclaringNodeType() + "': ambiguous child node definition. name must differ."; log.debug(msg); throw new ConstraintViolationException(msg); } } } } } else { existingDefs = new ArrayList<QItemDefinition>(); namedItemDefs.put(name, existingDefs); } existingDefs.add(qDef); } // residual item definitions defs = other.getUnnamedItemDefs(); for (int i = 0; i < defs.length; i++) { QItemDefinition qDef = defs[i]; if (includesNodeType(qDef.getDeclaringNodeType())) { // ignore redundant definitions continue; } for (QItemDefinition existing : unnamedItemDefs) { // compare with existing definition if (qDef.definesNode() == existing.definesNode()) { if (!qDef.definesNode()) { // property definition QPropertyDefinition pd = (QPropertyDefinition) qDef; QPropertyDefinition epd = (QPropertyDefinition) existing; // compare type & multiValued flag if (pd.getRequiredType() == epd.getRequiredType() && pd.isMultiple() == epd.isMultiple() && pd.getOnParentVersion() == epd.getOnParentVersion()) { // conflict // TODO: need to take more aspects into account // TODO: getMatchingPropDef needs to check this as well String msg = "A property definition in node type '" + qDef.getDeclaringNodeType() + "' conflicts with node type '" + existing.getDeclaringNodeType() + "': ambiguous residual property definition"; log.debug(msg); throw new ConstraintViolationException(msg); } } else { // child node definition QNodeDefinition nd = (QNodeDefinition) qDef; QNodeDefinition end = (QNodeDefinition) 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 '" + qDef.getDeclaringNodeType() + "' conflicts with node type '" + existing.getDeclaringNodeType() + "': ambiguous residual child node definition"; log.debug(msg); throw new ConstraintViolationException(msg); } } } } unnamedItemDefs.add(qDef); } 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]); } } } @Override protected Object clone() { EffectiveNodeTypeImpl clone = new EffectiveNodeTypeImpl(mergedNodeTypes, inheritedNodeTypes, allNodeTypes, namedItemDefs, unnamedItemDefs, supportedMixins); return clone; } }