/* * ModeShape (http://www.modeshape.org) * * 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 org.modeshape.jcr; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.jcr.ItemExistsException; import javax.jcr.PropertyType; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFormatException; import javax.jcr.nodetype.ConstraintViolationException; import javax.jcr.nodetype.InvalidNodeTypeDefinitionException; import javax.jcr.nodetype.NodeType; import javax.jcr.nodetype.NodeTypeDefinition; import javax.jcr.nodetype.PropertyDefinition; import org.modeshape.common.annotation.Immutable; import org.modeshape.common.collection.HashMultimap; import org.modeshape.common.collection.Multimap; import org.modeshape.common.i18n.I18n; import org.modeshape.common.util.CheckArg; import org.modeshape.jcr.cache.NodeKey; import org.modeshape.jcr.cache.SiblingCounter; import org.modeshape.jcr.value.Name; import org.modeshape.jcr.value.NameFactory; import org.modeshape.jcr.value.Path; import org.modeshape.jcr.value.StringFactory; @Immutable public class NodeTypes { public static interface Supplier { /** * Get the immutable cache of node types. * * @return the immutable node types cache; never null */ NodeTypes getNodeTypes(); } public static interface Listener { /** * Notification that the NodeTypes instance has changed. * * @param updatedNodeTypes the new NodeTypes instance; never null */ void notify( NodeTypes updatedNodeTypes ); } /** * List of ways to filter the returned property definitions * * @see NodeTypes#findPropertyDefinitions(List, Name, PropertyCardinality, List) */ public enum PropertyCardinality { SINGLE_VALUED_ONLY, MULTI_VALUED_ONLY, ANY } /** * List of ways to filter the returned node definitions */ public enum NodeCardinality { NO_SAME_NAME_SIBLINGS, SAME_NAME_SIBLINGS, ANY } private final Map<Name, JcrNodeType> nodeTypes = new HashMap<Name, JcrNodeType>(); private final Map<PropertyDefinitionId, JcrPropertyDefinition> propertyDefinitions = new HashMap<PropertyDefinitionId, JcrPropertyDefinition>(); private final Map<NodeDefinitionId, JcrNodeDefinition> childNodeDefinitions = new HashMap<NodeDefinitionId, JcrNodeDefinition>(); private final Collection<JcrNodeType> unmodifiableNodeTypes; private final Collection<JcrNodeType> unmodifiableMixinNodeTypes; private final Collection<JcrNodeType> unmodifiablePrimaryNodeTypes; private final Set<Name> unmodifiableNodeTypeNames; private final Set<Name> unmodifiableMixinTypeNames; private final int nodeTypesVersion; private final JcrNodeDefinition ntUnstructuredSnsChildDefinition; private final JcrNodeDefinition ntFileChildDefinition; private final JcrNodeDefinition ntFolderChildDefinition; private final ExecutionContext context; private final NameFactory nameFactory; /** * The set of node type names that require no extra work during pre-save operations, as long as nodes that have this primary * type do not have any mixins. Note that this contains all node types not in any of the other sets. */ private static final Set<Name> fullyDefinedNodeTypes = new HashSet<>(); private static final Set<Name> NONE = Collections.emptySet(); /** Thread local variable containing the last NodeDefinitionSet used */ private static final ThreadLocal<ReusableNodeDefinitionSet> nodeDefinitionSet = new ThreadLocal<ReusableNodeDefinitionSet>() { @Override protected ReusableNodeDefinitionSet initialValue() { return new EmptyNodeDefinitionSet(); } }; /** * The set of standard built-in NodeDefinitionSet */ private final List<ReusableNodeDefinitionSet> standardNodeDefinitionSets = new LinkedList<>(); /** * The set of names for the node types that are 'mix:created'. See {@link #isCreated(Name, Set)} */ private final Set<Name> createdNodeTypeNames = new HashSet<>(); /** * The set of names for the node types that are 'mix:lastModified'. See {@link #isLastModified(Name, Set)} */ private final Set<Name> lastModifiedNodeTypeNames = new HashSet<>(); /** * The set of names for the node types that are 'mix:mimeType'. See {@link #isNtResource(Name)} */ private final Set<Name> resourceNodeTypeNames = new HashSet<>(); /** * The set of names for the node types that are 'mix:etag'. See {@link #isETag(Name, Set)} */ private final Set<Name> etagNodeTypeNames = new HashSet<>(); /** * The set of names for the node types that are 'mix:versionable'. See {@link #isVersionable(Name, Collection)} */ private final Set<Name> versionableNodeTypeNames = new HashSet<>(); /** * The set of names for the node types that have child node definitions that allow same name siblings for every combination. * In other words, given any valid child, there will always be a satisfying child node definition that allows same name * siblings. For example, 'nt:unstructured' contains residual child node definitions that allow and disallow same name * siblings, so 'nt:unstructured' will always be in this set. If a node type is not in this set, then nodes of this type must * be checked for children to ensure the children satisfy the child node definitions. */ private final Set<Name> nodeTypeNamesThatAllowSameNameSiblings = new HashSet<>(); private final Set<Name> nodeTypeNamesThatAreReferenceable = new HashSet<>(); private final Set<Name> nodeTypeNamesThatAreShareable = new HashSet<>(); private final Set<Name> nodeTypeNamesWithNoChildNodeDefns = new HashSet<>(); private final Set<Name> nodeTypeNamesThatAreUnorderableCollections = new HashSet<>(); /** * The map of mandatory (and perhaps auto-created) property definitions for a node type keyed by the name of the node type. * See {@link #hasMandatoryPropertyDefinitions} */ private final Multimap<Name, JcrPropertyDefinition> mandatoryPropertiesNodeTypes = HashMultimap.create(); /** * The map of mandatory (and perhaps auto-created) child node definitions for a node type keyed by the name of the node type. * See {@link #hasMandatoryChildNodeDefinitions} */ private final Multimap<Name, JcrNodeDefinition> mandatoryChildrenNodeTypes = HashMultimap.create(); /** * The map of auto-created property definitions for a node type keyed by the name of the node type. See * {@link #hasMandatoryPropertyDefinitions} */ private final Multimap<Name, JcrPropertyDefinition> autoCreatedPropertiesNodeTypes = HashMultimap.create(); /** * The map of auto-created child node definitions for a node type keyed by the name of the node type. See * {@link #hasMandatoryChildNodeDefinitions} */ private final Multimap<Name, JcrNodeDefinition> autoCreatedChildrenNodeTypes = HashMultimap.create(); /** * A set of all the node types which are defined as non-queryable (noquery) */ private final Set<Name> nonQueryableNodeTypes = new HashSet<>(); protected NodeTypes( ExecutionContext context ) { this(context, null, 0); } protected NodeTypes( ExecutionContext context, Iterable<JcrNodeType> nodeTypes, int version ) { this.nodeTypesVersion = version; this.context = context; this.nameFactory = context.getValueFactories().getNameFactory(); Set<Name> mixinNames = new HashSet<Name>(); List<JcrNodeType> mixins = new ArrayList<JcrNodeType>(); List<JcrNodeType> primaries = new ArrayList<JcrNodeType>(); if (nodeTypes != null) { JcrNodeType ntUnstructured = null; for (JcrNodeType nodeType : nodeTypes) { boolean isUnorderedCollection = false; Name name = nodeType.getInternalName(); // Store the node type in the quick-lookup maps ... this.nodeTypes.put(name, nodeType); for (JcrNodeDefinition childDefinition : nodeType.childNodeDefinitions()) { this.childNodeDefinitions.put(childDefinition.getId(), childDefinition); } for (JcrPropertyDefinition propertyDefinition : nodeType.propertyDefinitions()) { this.propertyDefinitions.put(propertyDefinition.getId(), propertyDefinition); } if (nodeType.isMixin()) { mixins.add(nodeType); mixinNames.add(name); } else { primaries.add(nodeType); } if (name.equals(JcrNtLexicon.UNSTRUCTURED)) { ntUnstructured = nodeType; nodeTypeNamesThatAllowSameNameSiblings.add(name); } if (nodeType.isNodeType(JcrMixLexicon.REFERENCEABLE)) { nodeTypeNamesThatAreReferenceable.add(name); } if (nodeType.isNodeType(JcrMixLexicon.SHAREABLE)) { nodeTypeNamesThatAreShareable.add(name); } if (nodeType.isNodeType(ModeShapeLexicon.UNORDERED_COLLECTION)) { nodeTypeNamesThatAreUnorderableCollections.add(name); } boolean fullyDefined = true; if (nodeType.isNodeType(JcrMixLexicon.CREATED)) { createdNodeTypeNames.add(name); fullyDefined = false; } if (nodeType.isNodeType(JcrMixLexicon.LAST_MODIFIED)) { lastModifiedNodeTypeNames.add(name); fullyDefined = false; } if (nodeType.isNodeType(JcrNtLexicon.RESOURCE)) { resourceNodeTypeNames.add(name); fullyDefined = false; } if (nodeType.isNodeType(JcrMixLexicon.ETAG)) { etagNodeTypeNames.add(name); fullyDefined = false; } if (nodeType.isNodeType(JcrMixLexicon.VERSIONABLE)) { versionableNodeTypeNames.add(name); fullyDefined = false; } if (nodeType.isNodeType(ModeShapeLexicon.UNORDERED_COLLECTION)) { nodeTypeNamesThatAreUnorderableCollections.add(name); isUnorderedCollection = true; } for (JcrPropertyDefinition propDefn : nodeType.allPropertyDefinitions()) { if (propDefn.isMandatory() && !propDefn.isProtected()) { mandatoryPropertiesNodeTypes.put(name, propDefn); fullyDefined = false; } if (propDefn.isAutoCreated() && !propDefn.isProtected()) { autoCreatedPropertiesNodeTypes.put(name, propDefn); // This isn't used in the pre-save operations, since auto-created items should be set on node creation // fullDefined = false; } } Collection<JcrNodeDefinition> allChildNodeDefinitions = nodeType.allChildNodeDefinitions(); if (allChildNodeDefinitions.isEmpty()) nodeTypeNamesWithNoChildNodeDefns.add(name); boolean allowsSNS = false; boolean mixinWithNoChildNodeDefinitions = nodeType.isMixin() && allChildNodeDefinitions.isEmpty(); for (JcrNodeDefinition childDefn : allChildNodeDefinitions) { if (childDefn.isMandatory() && !childDefn.isProtected()) { mandatoryChildrenNodeTypes.put(name, childDefn); fullyDefined = false; } if (childDefn.isAutoCreated() && !childDefn.isProtected()) { autoCreatedChildrenNodeTypes.put(name, childDefn); // This isn't used in the pre-save operations, since auto-created items should be set on node creation // fullDefined = false; } if (childDefn.allowsSameNameSiblings()) { allowsSNS = true; } } if (!nodeType.isAbstract() && !isUnorderedCollection && (allowsSNS || mixinWithNoChildNodeDefinitions)) { nodeTypeNamesThatAllowSameNameSiblings.add(name); } if (fullyDefined) { fullyDefinedNodeTypes.add(name); } if (!nodeType.isQueryable()) { nonQueryableNodeTypes.add(name); } } assert ntUnstructured != null; // Find and cache the 'nt:unstructured' residual child node definition that allows multiple SNS ... Collection<JcrNodeDefinition> childDefns = ntUnstructured.allChildNodeDefinitions(JcrNodeType.RESIDUAL_NAME, true); assert childDefns.size() == 1; ntUnstructuredSnsChildDefinition = childDefns.iterator().next(); assert ntUnstructuredSnsChildDefinition != null; Collection<JcrNodeDefinition> fileChildDefns = getNodeType(JcrNtLexicon.FILE).allChildNodeDefinitions(); Collection<JcrNodeDefinition> folderChildDefns = getNodeType(JcrNtLexicon.FOLDER).allChildNodeDefinitions(); assert fileChildDefns.size() == 1; assert folderChildDefns.size() == 1; this.ntFileChildDefinition = fileChildDefns.iterator().next(); this.ntFolderChildDefinition = folderChildDefns.iterator().next(); // Add some standard node definition sets ... this.standardNodeDefinitionSets.add(new SingleNodeDefinitionSet(JcrNtLexicon.UNSTRUCTURED, NONE, ntUnstructuredSnsChildDefinition)); this.standardNodeDefinitionSets.add(new SingleNodeDefinitionSet(JcrNtLexicon.FILE, NONE, ntFileChildDefinition)); this.standardNodeDefinitionSets.add(new SingleNodeDefinitionSet(JcrNtLexicon.FOLDER, NONE, ntFolderChildDefinition)); } else { this.ntUnstructuredSnsChildDefinition = null; this.ntFileChildDefinition = null; this.ntFolderChildDefinition = null; } this.unmodifiableNodeTypes = Collections.unmodifiableCollection(this.nodeTypes.values()); this.unmodifiableNodeTypeNames = Collections.unmodifiableSet(this.nodeTypes.keySet()); this.unmodifiableMixinTypeNames = Collections.unmodifiableSet(mixinNames); this.unmodifiableMixinNodeTypes = Collections.unmodifiableList(mixins); this.unmodifiablePrimaryNodeTypes = Collections.unmodifiableList(primaries); } /** * Obtain a new version of this cache with the specified node types removed from the new cache. * * @param removedNodeTypes the node types that are to be removed from the resulting cache; may not be null but may be empty * @return the resulting cache that contains all of the node types within this cache but without the supplied node types; * never null */ protected NodeTypes without( Collection<JcrNodeType> removedNodeTypes ) { if (removedNodeTypes.isEmpty()) return this; Collection<JcrNodeType> nodeTypes = new HashSet<JcrNodeType>(this.nodeTypes.values()); nodeTypes.removeAll(removedNodeTypes); return new NodeTypes(this.context, nodeTypes, getVersion() + 1); } /** * Obtain a new version of this cache with the specified node types added to the new cache. * * @param addedNodeTypes the node types that are to be added to the resulting cache; may not be null but may be empty * @return the resulting cache that contains all of the node types within this cache and the supplied node types; never null */ protected NodeTypes with( Collection<JcrNodeType> addedNodeTypes ) { if (addedNodeTypes.isEmpty()) return this; Collection<JcrNodeType> nodeTypes = new HashSet<JcrNodeType>(this.nodeTypes.values()); // if there are updated node types, remove them first (hashcode is based on name alone), // else addAll() will ignore the changes. nodeTypes.removeAll(addedNodeTypes); nodeTypes.addAll(addedNodeTypes); return new NodeTypes(this.context, nodeTypes, getVersion() + 1); } /** * @return nameFactory */ protected final NameFactory nameFactory() { return nameFactory; } /** * Get the version number of this cache. This essentially acts as an ETag, allowing other components to cache node type * information as long as the version number stays the same. * * @return the version number of this cache */ public int getVersion() { return nodeTypesVersion; } /** * Determine if the named node type does not appear in any of the other sets. Such node types are fully-defined, in that nodes * using them require no additional processing prior to save. * <p> * Note that this method's signature is different from the other methods. This is because a node's primary type and mixin * types must all be fully-defined types. * </p> * * @param primaryTypeName the name of the primary node type; may not be null * @param mixinTypeNames the set of mixin type names; may be null or empty * @return true if the named node type is fully-defined, or false otherwise */ public boolean isFullyDefinedType( Name primaryTypeName, Set<Name> mixinTypeNames ) { if (!fullyDefinedNodeTypes.contains(primaryTypeName)) return false; if (!mixinTypeNames.isEmpty()) { for (Name nodeTypeName : mixinTypeNames) { if (!fullyDefinedNodeTypes.contains(nodeTypeName)) return false; } } return true; } public Set<Name> getAllSubtypes( Name nodeTypeName ) { Set<Name> subtypes = new HashSet<>(); JcrNodeType type = getNodeType(nodeTypeName); if (type != null) { subtypes.add(nodeTypeName); for (JcrNodeType subtype : subtypesFor(type)) { subtypes.add(subtype.getInternalName()); } } return Collections.unmodifiableSet(subtypes); } /** * Determine whether the node's given node type matches or extends the node type with the supplied name. * * @param nodeTypeName the name of the node type of a node; may not be null * @param candidateSupertypeName the name of the potential supertype node type; may not be null * @return true if the node type does extend or match the node type given by the supplied name, or false otherwise */ public boolean isTypeOrSubtype( Name nodeTypeName, Name candidateSupertypeName ) { if (JcrNtLexicon.BASE.equals(candidateSupertypeName)) { // If the candidate is 'nt:base', then every node type is a subtype ... return true; } if (nodeTypeName.equals(candidateSupertypeName)) return true; JcrNodeType nodeType = getNodeType(nodeTypeName); return nodeType != null && nodeType.isNodeType(candidateSupertypeName); } /** * Determine whether at least one of the node's given node types matches or extends the node type with the supplied name. * * @param nodeTypeNames the names of the node types of a node; may not be null * @param candidateSupertypeName the name of the potential supertype node type; may not be null * @return true if the node type does extend or match the node type given by at least one of the supplied names, or false * otherwise */ public boolean isTypeOrSubtype( Set<Name> nodeTypeNames, Name candidateSupertypeName ) { for (Name nodeTypeName : nodeTypeNames) { if (isTypeOrSubtype(nodeTypeName, candidateSupertypeName)) return true; } return false; } /** * Determine whether at least one of the node's given node types matches or extends the node type with the supplied name. * * @param nodeTypeNames the names of the node types of a node; may not be null * @param candidateSupertypeName the name of the potential supertype node type; may not be null * @return true if the node type does extend or match the node type given by at least one of the supplied names, or false * otherwise */ public boolean isTypeOrSubtype( Name[] nodeTypeNames, Name candidateSupertypeName ) { for (Name nodeTypeName : nodeTypeNames) { if (isTypeOrSubtype(nodeTypeName, candidateSupertypeName)) return true; } return false; } /** * Determine if at least one of the named primary node type or mixin types is or subtypes the 'mix:created' mixin type. * * @param primaryType the primary type name; may not be null * @param mixinTypes the mixin type names; may be null or empty * @return true if any of the named node types is a created type, or false if there are none */ public boolean isCreated( Name primaryType, Set<Name> mixinTypes ) { if (createdNodeTypeNames.contains(primaryType)) return true; if (mixinTypes != null) { for (Name mixinType : mixinTypes) { if (createdNodeTypeNames.contains(mixinType)) return true; } } return false; } /** * Determine if at least one of the named primary node type or mixin types is or subtypes the 'mix:lastModified' mixin type. * * @param primaryType the primary type name; may not be null * @param mixinTypes the mixin type names; may be null or empty * @return true if any of the named node types is a last-modified type, or false if there are none */ public boolean isLastModified( Name primaryType, Set<Name> mixinTypes ) { if (lastModifiedNodeTypeNames.contains(primaryType)) return true; if (mixinTypes != null) { for (Name mixinType : mixinTypes) { if (lastModifiedNodeTypeNames.contains(mixinType)) return true; } } return false; } /** * Determine if the named primary node type is or subtypes the 'nt:resource' node type. * * @param primaryType the primary type name; may not be null * @return true if the primary node type is an 'nt:resource' node type (or subtype), or false otherwise */ public boolean isNtResource( Name primaryType ) { // 'nt:resource' is a node type (not a mixin), so it can't appear in the mixin types ... return resourceNodeTypeNames.contains(primaryType); } /** * Determine if at least one of the named primary node type or mixin types is or subtypes the 'mix:etag' mixin type. * * @param primaryType the primary type name; may not be null * @param mixinTypes the mixin type names; may be null or empty * @return true if any of the named node types has an ETag, or false if there are none */ public boolean isETag( Name primaryType, Set<Name> mixinTypes ) { if (etagNodeTypeNames.contains(primaryType)) return true; if (mixinTypes != null) { for (Name mixinType : mixinTypes) { if (etagNodeTypeNames.contains(mixinType)) return true; } } return false; } /** * Determine if at least one of the named primary node type or mixin types is or subtypes the 'mix:referenceable' mixin type. * * @param primaryType the primary type name; may not be null * @param mixinTypes the mixin type names; may be null or empty * @return true if the primary node type is an 'mix:referenceable' node type (or subtype), or false otherwise */ public boolean isReferenceable( Name primaryType, Set<Name> mixinTypes ) { if (nodeTypeNamesThatAreReferenceable.contains(primaryType)) return true; if (mixinTypes != null) { for (Name mixinType : mixinTypes) { if (nodeTypeNamesThatAreReferenceable.contains(mixinType)) return true; } } return false; } /** * Determine if at least one of the named primary node type or mixin types is or subtypes the 'mix:shareable' mixin type. * * @param primaryType the primary type name; may not be null * @param mixinTypes the mixin type names; may be null or empty * @return true if the primary node type is an 'mix:shareable' node type (or subtype), or false otherwise */ public boolean isShareable( Name primaryType, Set<Name> mixinTypes ) { if (nodeTypeNamesThatAreShareable.contains(primaryType)) return true; if (mixinTypes != null) { for (Name mixinType : mixinTypes) { if (nodeTypeNamesThatAreShareable.contains(mixinType)) return true; } } return false; } /** * Determine if either the primary type or any of the mixin types allows SNS. * * @param primaryType the primary type name; may not be null * @param mixinTypes the mixin type names; may be null or empty * @return {@code true} if either the primary type or any of the mixin types allows SNS. If neither allow SNS, * this will return {@code false} */ public boolean allowsNameSiblings( Name primaryType, Set<Name> mixinTypes ) { if (isUnorderedCollection(primaryType, mixinTypes)) { // regardless of the actual types, if at least one of them is an unordered collection, SNS are not allowed return false; } if (nodeTypeNamesThatAllowSameNameSiblings.contains(primaryType)) return true; if (mixinTypes != null && !mixinTypes.isEmpty()) { for (Name mixinType : mixinTypes) { if (nodeTypeNamesThatAllowSameNameSiblings.contains(mixinType)) return true; } } return false; } /** * Determine if the named node type is or subtypes the 'mix:versionable' mixin type. * * @param nodeTypeName the node type name; may be null * @return true if any of the named node type is versionable, or false otherwise */ public boolean isVersionable( Name nodeTypeName ) { return nodeTypeName != null && versionableNodeTypeNames.contains(nodeTypeName); } /** * Determine if the named node type or any of the mixin types subtypes the 'mode:unorderedCollection' type. * * @param nodeTypeName the node type name; may be null * @param mixinTypes the mixin type names; may be null or empty * @return true if any of the named node type is an unordered collection, or false otherwise */ public boolean isUnorderedCollection( Name nodeTypeName, Collection<Name> mixinTypes ) { if (nodeTypeName != null && nodeTypeNamesThatAreUnorderableCollections.contains(nodeTypeName)) { return true; } if (mixinTypes != null && !mixinTypes.isEmpty()) { for (Name mixin : mixinTypes) { if (nodeTypeNamesThatAreUnorderableCollections.contains(mixin)) { return true; } } } return false; } /** * Determine the length of a bucket ID for an unordered collection, based on its type and possible mixins. * * @param nodeTypeName the primary type of the collection; may not be null. * @param mixinTypes the mixin type names; may be null or empty * @return the order of magnitude, as a power of 16 */ public int getBucketIdLengthForUnorderedCollection( Name nodeTypeName, Set<Name> mixinTypes ) { Set<Name> allTypes = new LinkedHashSet<>(); allTypes.add(nodeTypeName); if (mixinTypes != null && !mixinTypes.isEmpty()) { allTypes.addAll(mixinTypes); } for (Name typeName : allTypes) { if (isTypeOrSubtype(typeName, ModeShapeLexicon.TINY_UNORDERED_COLLECTION)) { return 1; } else if (isTypeOrSubtype(typeName, ModeShapeLexicon.SMALL_UNORDERED_COLLECTION)) { return 2; } else if (isTypeOrSubtype(typeName, ModeShapeLexicon.LARGE_UNORDERED_COLLECTION)) { return 3; } else if (isTypeOrSubtype(typeName, ModeShapeLexicon.HUGE_UNORDERED_COLLECTION)) { return 4; } } throw new IllegalArgumentException("None of the node types are known unordered collection types: " + allTypes); } /** * Determine if at least one of the named primary node type or mixin types is or subtypes the 'mix:versionable' mixin type. * * @param primaryType the primary type name; may be null * @param mixinTypes the mixin type names; may not be null but may be empty * @return true if any of the named node types is versionable, or false if there are none */ public boolean isVersionable( Name primaryType, Collection<Name> mixinTypes ) { if (primaryType != null && versionableNodeTypeNames.contains(primaryType)) return true; for (Name mixinType : mixinTypes) { if (versionableNodeTypeNames.contains(mixinType)) return true; } return false; } /** * Determine if the named property on the node type is a reference property. * * @param nodeTypeName the name of the node type; may not be null * @param propertyName the name of the property definition; may not be null * @return true if the property is a {@link PropertyType#REFERENCE}, {@link PropertyType#WEAKREFERENCE}, or * {@link org.modeshape.jcr.api.PropertyType#SIMPLE_REFERENCE}, or false otherwise */ public boolean isReferenceProperty( Name nodeTypeName, Name propertyName ) { JcrNodeType type = getNodeType(nodeTypeName); if (type != null) { for (JcrPropertyDefinition propDefn : type.allPropertyDefinitions(propertyName)) { int requiredType = propDefn.getRequiredType(); if (requiredType == PropertyType.REFERENCE || requiredType == PropertyType.WEAKREFERENCE || requiredType == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE) { return true; } } } return false; } /** * Determine if the named primary node type or mixin types has at least one mandatory property definitions declared on it or * any of its supertypes. * * @param primaryType the primary type name; may not be null * @param mixinTypes the mixin type names; may not be null but may be empty * @return true if any of the named node types has one or more mandatory property definitions, or false if there are none */ public boolean hasMandatoryPropertyDefinitions( Name primaryType, Set<Name> mixinTypes ) { if (mandatoryPropertiesNodeTypes.containsKey(primaryType)) return true; for (Name mixinType : mixinTypes) { if (mandatoryPropertiesNodeTypes.containsKey(mixinType)) return true; } return false; } /** * Determine if the named primary node type or mixin types has at least one mandatory child node definitions declared on it or * any of its supertypes. * * @param primaryType the primary type name; may not be null * @param mixinTypes the mixin type names; may not be null but may be empty * @return true if any of the the named node types has one or more mandatory child node definitions, or false if there are * none */ public boolean hasMandatoryChildNodeDefinitions( Name primaryType, Set<Name> mixinTypes ) { if (mandatoryChildrenNodeTypes.containsKey(primaryType)) return true; for (Name mixinType : mixinTypes) { if (mandatoryChildrenNodeTypes.containsKey(mixinType)) return true; } return false; } /** * Get the mandatory property definitions for a node with the named primary type and mixin types. Note that the * {@link #hasMandatoryPropertyDefinitions(Name, Set)} method should first be called with the primary type and mixin types; if * that method returns <code>true</code>, then this method will never return an empty collection. * * @param primaryType the primary type name; may not be null * @param mixinTypes the mixin type names; may not be null but may be empty * @return the collection of mandatory property definitions; never null but possibly empty */ public Collection<JcrPropertyDefinition> getMandatoryPropertyDefinitions( Name primaryType, Set<Name> mixinTypes ) { if (mixinTypes.isEmpty()) { return mandatoryPropertiesNodeTypes.get(primaryType); } Set<JcrPropertyDefinition> defn = new HashSet<JcrPropertyDefinition>(); defn.addAll(mandatoryPropertiesNodeTypes.get(primaryType)); for (Name mixinType : mixinTypes) { defn.addAll(mandatoryPropertiesNodeTypes.get(mixinType)); } return defn; } /** * Get the mandatory child node definitions for a node with the named primary type and mixin types. Note that the * {@link #hasMandatoryChildNodeDefinitions(Name, Set)} method should first be called with the primary type and mixin types; * if that method returns <code>true</code>, then this method will never return an empty collection. * * @param primaryType the primary type name; may not be null * @param mixinTypes the mixin type names; may not be null but may be empty * @return the collection of mandatory child node definitions; never null but possibly empty */ public Collection<JcrNodeDefinition> getMandatoryChildNodeDefinitions( Name primaryType, Set<Name> mixinTypes ) { if (mixinTypes.isEmpty()) { return mandatoryChildrenNodeTypes.get(primaryType); } Set<JcrNodeDefinition> defn = new HashSet<JcrNodeDefinition>(); defn.addAll(mandatoryChildrenNodeTypes.get(primaryType)); for (Name mixinType : mixinTypes) { defn.addAll(mandatoryChildrenNodeTypes.get(mixinType)); } return defn; } /** * Get the auto-created property definitions for the named node type. This method is used when * {@link AbstractJcrNode#addChildNode(Name, Name, NodeKey, boolean, boolean) creating nodes}, which only needs the * auto-created properties for the primary type. It's also used when {@link AbstractJcrNode#addMixin(String) adding a mixin}. * * @param nodeType the node type name; may not be null * @return the collection of auto-created property definitions; never null but possibly empty */ public Collection<JcrPropertyDefinition> getAutoCreatedPropertyDefinitions( Name nodeType ) { return autoCreatedPropertiesNodeTypes.get(nodeType); } /** * Get the auto-created child node definitions for the named node type. This method is used when * {@link AbstractJcrNode#addChildNode(Name, Name, NodeKey, boolean, boolean) creating nodes}, which only needs the * auto-created properties for the primary type. It's also used when {@link AbstractJcrNode#addMixin(String) adding a mixin}. * * @param nodeType the node type name; may not be null * @return the collection of auto-created child node definitions; never null but possibly empty */ public Collection<JcrNodeDefinition> getAutoCreatedChildNodeDefinitions( Name nodeType ) { return autoCreatedChildrenNodeTypes.get(nodeType); } /** * Return the immutable list of node types that are currently registered in this node type manager. * * @return the immutable collection of (immutable) node types; never null */ public Collection<JcrNodeType> getAllNodeTypes() { return this.unmodifiableNodeTypes; } /** * Return an immutable snapshot of the names of the node types currently registered in this node type manager. * * @return the immutable collection of (immutable) node type names; never null */ public Set<Name> getAllNodeTypeNames() { return this.unmodifiableNodeTypeNames; } /** * Return an immutable snapshot of the mixin node types that are currently registered in this node type manager. * * @return the immutable collection of (immutable) mixin node types; never null * @see #getPrimaryNodeTypes() */ public Collection<JcrNodeType> getMixinNodeTypes() { return this.unmodifiableMixinNodeTypes; } /** * Determine whether the node type given by the supplied name is a mixin node type. * * @param nodeTypeName the name of the node type * @return true if there is an existing mixin node type with the supplied name, or false otherwise */ public boolean isMixin( Name nodeTypeName ) { return unmodifiableMixinTypeNames.contains(nodeTypeName); } /** * Return an immutable snapshot of the primary node types that are currently registered in this node type manager. * * @return the immutable collection of (immutable) primary node types; never null * @see #getMixinNodeTypes() */ public Collection<JcrNodeType> getPrimaryNodeTypes() { return this.unmodifiablePrimaryNodeTypes; } public JcrPropertyDefinition getPropertyDefinition( PropertyDefinitionId id ) { return propertyDefinitions.get(id); } public Collection<JcrPropertyDefinition> getAllPropertyDefinitions() { return propertyDefinitions.values(); } public JcrNodeDefinition getChildNodeDefinition( NodeDefinitionId id ) { return childNodeDefinitions.get(id); } JcrNodeType getNodeType( Name nodeTypeName ) { return nodeTypes.get(nodeTypeName); } public NodeType getJcrNodeType( Name nodeTypeName ) { return nodeTypes.get(nodeTypeName); } /** * Check if the node type and mixin types are queryable or not. * * @param nodeTypeName a {@link Name}, never {@code null} * @param mixinTypes the mixin type names; may not be null but may be empty * * @return {@code false} if at least one of the node types is not queryable, {@code true} otherwise */ public boolean isQueryable(Name nodeTypeName, Set<Name> mixinTypes) { if (nonQueryableNodeTypes.contains(nodeTypeName)) { return false; } if (!mixinTypes.isEmpty()) { for (Name mixinType : mixinTypes) { if (nonQueryableNodeTypes.contains(mixinType)) { return false; } } } return true; } /** * Tests if the named node type is registered. * <p> * The return value of this method is equivalent to {@code getNodeType(nodeTypeName) != null}, although the implementation is * marginally more efficient that this approach. * </p> * * @param nodeTypeName the name of the node type to check * @return true if a node type with the given name is registered, false otherwise */ boolean hasNodeType( Name nodeTypeName ) { return nodeTypes.containsKey(nodeTypeName); } /** * Searches the supplied primary node type and the mixin node types for a property definition that is the best match for the * given property name, property type, and value. * <p> * This method first attempts to find a single-valued property definition with the supplied property name and * {@link Value#getType() value's property type} in the primary type, skipping any property definitions that are protected. * The property definition is returned if it has a matching type (or has an {@link PropertyType#UNDEFINED undefined property * type}) and the value satisfies the {@link PropertyDefinition#getValueConstraints() definition's constraints}. Otherwise, * the process continues with each of the mixin types, in the order they are named. * </p> * <p> * If no matching property definition could be found (and <code>checkMultiValuedDefinitions</code> parameter is * <code>true</code>), the process is repeated except with multi-valued property definitions with the same name, property * type, and compatible constraints, starting with the primary type and continuing with each mixin type. * </p> * <p> * If no matching property definition could be found, and the process repeats by searching the primary type (and then mixin * types) for single-valued property definitions with a compatible type, where the values can be safely cast to the * definition's property type and still satisfy the definition's constraints. * </p> * <p> * If no matching property definition could be found, the previous step is repeated with multi-valued property definitions. * </p> * <p> * If no matching property definition could be found (and the supplied property name is not the residual name), the whole * process repeats for residual property definitions (e.g., those that are defined with a {@link JcrNodeType#RESIDUAL_NAME "*" * name}). * </p> * <p> * Finally, if no satisfactory property definition could be found, this method returns null. * </p> * * @param session the session in which the constraints are to be checked; may not be null * @param primaryTypeName the name of the primary type; may not be null * @param mixinTypeNames the names of the mixin types; may be null or empty if there are no mixins to include in the search * @param propertyName the name of the property for which the definition should be retrieved. This method will automatically * look for residual definitions, but you can use {@link JcrNodeType#RESIDUAL_ITEM_NAME} to retrieve only the best * residual property definition (if any). * @param value the value, or null if the property is being removed * @param checkMultiValuedDefinitions true if the type's multi-valued property definitions should be considered, or false if * only single-value property definitions should be considered * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if * this operation is being done from within internal implementations * @return the best property definition, or <code>null</code> if no property definition allows the property with the supplied * name, type and number of values */ JcrPropertyDefinition findPropertyDefinition( JcrSession session, Name primaryTypeName, Collection<Name> mixinTypeNames, Name propertyName, Value value, boolean checkMultiValuedDefinitions, boolean skipProtected ) { return findPropertyDefinition(session, primaryTypeName, mixinTypeNames, propertyName, value, checkMultiValuedDefinitions, skipProtected, true); } /** * Searches the supplied primary node type and the mixin node types for a property definition that is the best match for the * given property name, property type, and value. * <p> * This method first attempts to find a single-valued property definition with the supplied property name and * {@link Value#getType() value's property type} in the primary type, skipping any property definitions that are protected. * The property definition is returned if it has a matching type (or has an {@link PropertyType#UNDEFINED undefined property * type}) and the value satisfies the {@link PropertyDefinition#getValueConstraints() definition's constraints}. Otherwise, * the process continues with each of the mixin types, in the order they are named. * </p> * <p> * If no matching property definition could be found (and <code>checkMultiValuedDefinitions</code> parameter is * <code>true</code>), the process is repeated except with multi-valued property definitions with the same name, property * type, and compatible constraints, starting with the primary type and continuing with each mixin type. * </p> * <p> * If no matching property definition could be found, and the process repeats by searching the primary type (and then mixin * types) for single-valued property definitions with a compatible type, where the values can be safely cast to the * definition's property type and still satisfy the definition's constraints. * </p> * <p> * If no matching property definition could be found, the previous step is repeated with multi-valued property definitions. * </p> * <p> * If no matching property definition could be found (and the supplied property name is not the residual name), the whole * process repeats for residual property definitions (e.g., those that are defined with a {@link JcrNodeType#RESIDUAL_NAME "*" * name}). * </p> * <p> * Finally, if no satisfactory property definition could be found, this method returns null. * </p> * * @param session the session in which the constraints are to be checked; may not be null * @param primaryTypeName the name of the primary type; may not be null * @param mixinTypeNames the names of the mixin types; may be null or empty if there are no mixins to include in the search * @param propertyName the name of the property for which the definition should be retrieved. This method will automatically * look for residual definitions, but you can use {@link JcrNodeType#RESIDUAL_ITEM_NAME} to retrieve only the best * residual property definition (if any). * @param value the value, or null if the property is being removed * @param checkMultiValuedDefinitions true if the type's multi-valued property definitions should be considered, or false if * only single-value property definitions should be considered * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if * this operation is being done from within internal implementations * @param checkTypeAndConstraints true if the type and constraints of the property definition should be checked, or false * otherwise * @return the best property definition, or <code>null</code> if no property definition allows the property with the supplied * name, type and number of values */ JcrPropertyDefinition findPropertyDefinition( JcrSession session, Name primaryTypeName, Collection<Name> mixinTypeNames, Name propertyName, Value value, boolean checkMultiValuedDefinitions, boolean skipProtected, boolean checkTypeAndConstraints ) { boolean setToEmpty = value == null; /* * We use this flag to indicate that there was a definition encountered with the same name. If * a named definition (or definitions - for example the same node type could define a LONG and BOOLEAN * version of the same property) is encountered and no match is found for the name, then processing should not * proceed. If processing did proceed, a residual definition might be found and matched. This would * lead to a situation where a node defined a type for a named property, but contained a property with * the same name and the wrong type. */ boolean matchedOnName = false; // Look for a single-value property definition on the primary type that matches by name and type ... JcrNodeType primaryType = getNodeType(primaryTypeName); if (primaryType != null) { for (JcrPropertyDefinition definition : primaryType.allSingleValuePropertyDefinitions(propertyName)) { matchedOnName = true; // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; if (setToEmpty) { if (!definition.isMandatory()) return definition; // Otherwise this definition doesn't work, so continue with the next ... continue; } assert value != null; // We can use the definition if it matches the type and satisfies the constraints ... int type = definition.getRequiredType(); // Don't check constraints on reference properties if (type == PropertyType.REFERENCE && type == value.getType()) return definition; if (type == PropertyType.WEAKREFERENCE && type == value.getType()) return definition; if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && type == value.getType()) return definition; if (type == PropertyType.UNDEFINED || type == value.getType()) { if (!checkTypeAndConstraints) return definition; if (definition.satisfiesConstraints(value, session)) return definition; } } if (matchedOnName) { if (value != null) { for (JcrPropertyDefinition definition : primaryType.allSingleValuePropertyDefinitions(propertyName)) { // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; // Don't check constraints on reference properties int type = definition.getRequiredType(); if (type == PropertyType.REFERENCE && definition.canCastToType(value)) { return definition; } if (type == PropertyType.WEAKREFERENCE && definition.canCastToType(value)) { return definition; } if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && definition.canCastToType(value)) { return definition; } if (!checkTypeAndConstraints) return definition; if (definition.canCastToTypeAndSatisfyConstraints(value, session)) return definition; } } if (checkMultiValuedDefinitions) { // Look for a multi-value property definition on the primary type that matches by name and type ... for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) { // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; if (setToEmpty) { if (!definition.isMandatory()) return definition; // Otherwise this definition doesn't work, so continue with the next ... continue; } assert value != null; // We can use the definition if it matches the type and satisfies the constraints ... int type = definition.getRequiredType(); // Don't check constraints on reference properties if (type == PropertyType.REFERENCE && type == value.getType()) return definition; if (type == PropertyType.WEAKREFERENCE && type == value.getType()) return definition; if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && type == value.getType()) return definition; if (type == PropertyType.UNDEFINED || type == value.getType()) { if (!checkTypeAndConstraints) return definition; if (definition.satisfiesConstraints(value, session)) return definition; } } if (value != null) { for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) { // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; assert definition.getRequiredType() != PropertyType.UNDEFINED; // Don't check constraints on reference properties int type = definition.getRequiredType(); if (type == PropertyType.REFERENCE && definition.canCastToType(value)) { return definition; } if (type == PropertyType.WEAKREFERENCE && definition.canCastToType(value)) { return definition; } if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && definition.canCastToType(value)) { return definition; } if (!checkTypeAndConstraints) return definition; if (definition.canCastToTypeAndSatisfyConstraints(value, session)) return definition; } } } return null; } } // Look for a single-value property definition on the mixin types that matches by name and type ... List<JcrNodeType> mixinTypes = null; if (mixinTypeNames != null) { mixinTypes = new LinkedList<JcrNodeType>(); for (Name mixinTypeName : mixinTypeNames) { JcrNodeType mixinType = getNodeType(mixinTypeName); if (mixinType == null) continue; mixinTypes.add(mixinType); for (JcrPropertyDefinition definition : mixinType.allSingleValuePropertyDefinitions(propertyName)) { matchedOnName = true; // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; if (setToEmpty) { if (!definition.isMandatory()) return definition; // Otherwise this definition doesn't work, so continue with the next ... continue; } assert value != null; // We can use the definition if it matches the type and satisfies the constraints ... int type = definition.getRequiredType(); // Don't check constraints on reference properties if (type == PropertyType.REFERENCE && type == value.getType()) return definition; if (type == PropertyType.WEAKREFERENCE && type == value.getType()) return definition; if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && type == value.getType()) return definition; if (type == PropertyType.UNDEFINED || type == value.getType()) { if (!checkTypeAndConstraints) return definition; if (definition.satisfiesConstraints(value, session)) return definition; } } if (matchedOnName) { if (value != null) { for (JcrPropertyDefinition definition : mixinType.allSingleValuePropertyDefinitions(propertyName)) { // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; assert definition.getRequiredType() != PropertyType.UNDEFINED; // Don't check constraints on reference properties int type = definition.getRequiredType(); if (type == PropertyType.REFERENCE && definition.canCastToType(value)) { return definition; } if (type == PropertyType.WEAKREFERENCE && definition.canCastToType(value)) { return definition; } if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && definition.canCastToType(value)) { return definition; } if (!checkTypeAndConstraints) return definition; if (definition.canCastToTypeAndSatisfyConstraints(value, session)) return definition; } } if (checkMultiValuedDefinitions) { for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) { // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; if (setToEmpty) { if (!definition.isMandatory()) return definition; // Otherwise this definition doesn't work, so continue with the next ... continue; } assert value != null; // We can use the definition if it matches the type and satisfies the constraints ... int type = definition.getRequiredType(); // Don't check constraints on reference properties if (type == PropertyType.REFERENCE && type == value.getType()) return definition; if (type == PropertyType.WEAKREFERENCE && type == value.getType()) return definition; if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && type == value.getType()) return definition; if (type == PropertyType.UNDEFINED || type == value.getType()) { if (!checkTypeAndConstraints) return definition; if (definition.satisfiesConstraints(value, session)) return definition; } } if (value != null) { for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) { matchedOnName = true; // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; assert definition.getRequiredType() != PropertyType.UNDEFINED; // Don't check constraints on reference properties int type = definition.getRequiredType(); if (type == PropertyType.REFERENCE && definition.canCastToType(value)) { return definition; } if (type == PropertyType.WEAKREFERENCE && definition.canCastToType(value)) { return definition; } if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && definition.canCastToType(value)) { return definition; } if (!checkTypeAndConstraints) return definition; if (definition.canCastToTypeAndSatisfyConstraints(value, session)) return definition; } } } return null; } } } if (checkMultiValuedDefinitions) { if (primaryType != null) { // Look for a multi-value property definition on the primary type that matches by name and type ... for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) { matchedOnName = true; // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; if (setToEmpty) { if (!definition.isMandatory()) return definition; // Otherwise this definition doesn't work, so continue with the next ... continue; } assert value != null; // We can use the definition if it matches the type and satisfies the constraints ... int type = definition.getRequiredType(); // Don't check constraints on reference properties if (type == PropertyType.REFERENCE && type == value.getType()) return definition; if (type == PropertyType.WEAKREFERENCE && type == value.getType()) return definition; if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && type == value.getType()) return definition; if (type == PropertyType.UNDEFINED || type == value.getType()) { if (!checkTypeAndConstraints) return definition; if (definition.satisfiesConstraints(value, session)) return definition; } } if (value != null) { for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) { matchedOnName = true; // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; assert definition.getRequiredType() != PropertyType.UNDEFINED; // Don't check constraints on reference properties int type = definition.getRequiredType(); if (type == PropertyType.REFERENCE && definition.canCastToType(value)) { return definition; } if (type == PropertyType.WEAKREFERENCE && definition.canCastToType(value)) { return definition; } if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && definition.canCastToType(value)) { return definition; } if (!checkTypeAndConstraints) return definition; if (definition.canCastToTypeAndSatisfyConstraints(value, session)) return definition; } } } if (matchedOnName) return null; if (mixinTypeNames != null) { mixinTypes = new LinkedList<JcrNodeType>(); for (Name mixinTypeName : mixinTypeNames) { JcrNodeType mixinType = getNodeType(mixinTypeName); if (mixinType == null) continue; mixinTypes.add(mixinType); for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) { matchedOnName = true; // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; if (setToEmpty) { if (!definition.isMandatory()) return definition; // Otherwise this definition doesn't work, so continue with the next ... continue; } assert value != null; // We can use the definition if it matches the type and satisfies the constraints ... int type = definition.getRequiredType(); // Don't check constraints on reference properties if (type == PropertyType.REFERENCE && type == value.getType()) return definition; if (type == PropertyType.WEAKREFERENCE && type == value.getType()) return definition; if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && type == value.getType()) return definition; if (type == PropertyType.UNDEFINED || type == value.getType()) { if (!checkTypeAndConstraints) return definition; if (definition.satisfiesConstraints(value, session)) return definition; } } if (value != null) { for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) { matchedOnName = true; // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; assert definition.getRequiredType() != PropertyType.UNDEFINED; // Don't check constraints on reference properties int type = definition.getRequiredType(); if (type == PropertyType.REFERENCE && definition.canCastToType(value)) { return definition; } if (type == PropertyType.WEAKREFERENCE && definition.canCastToType(value)) { return definition; } if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && definition.canCastToType(value)) { return definition; } if (!checkTypeAndConstraints) return definition; if (definition.canCastToTypeAndSatisfyConstraints(value, session)) return definition; } } } } if (matchedOnName) return null; } // Nothing was found, so look for residual property definitions ... if (!propertyName.equals(JcrNodeType.RESIDUAL_NAME)) return findPropertyDefinition(session, primaryTypeName, mixinTypeNames, JcrNodeType.RESIDUAL_NAME, value, checkMultiValuedDefinitions, skipProtected, checkTypeAndConstraints); return null; } /** * Searches the supplied primary node type and the mixin node types for a property definition that is the best match for the * given property name, property type, and value. * <p> * This method first attempts to find a single-valued property definition with the supplied property name and * {@link Value#getType() value's property type} in the primary type, skipping any property definitions that are protected. * The property definition is returned if it has a matching type (or has an {@link PropertyType#UNDEFINED undefined property * type}) and the value satisfies the {@link PropertyDefinition#getValueConstraints() definition's constraints}. Otherwise, * the process continues with each of the mixin types, in the order they are named. * </p> * <p> * If no matching property definition could be found (and <code>checkMultiValuedDefinitions</code> parameter is * <code>true</code>), the process is repeated except with multi-valued property definitions with the same name, property * type, and compatible constraints, starting with the primary type and continuing with each mixin type. * </p> * <p> * If no matching property definition could be found, and the process repeats by searching the primary type (and then mixin * types) for single-valued property definitions with a compatible type, where the values can be safely cast to the * definition's property type and still satisfy the definition's constraints. * </p> * <p> * If no matching property definition could be found, the previous step is repeated with multi-valued property definitions. * </p> * <p> * If no matching property definition could be found (and the supplied property name is not the residual name), the whole * process repeats for residual property definitions (e.g., those that are defined with a {@link JcrNodeType#RESIDUAL_NAME "*" * name}). * </p> * <p> * Finally, if no satisfactory property definition could be found, this method returns null. * </p> * * @param session the session in which the constraints are to be checked; may not be null * @param primaryTypeName the name of the primary type; may not be null * @param mixinTypeNames the names of the mixin types; may be null or empty if there are no mixins to include in the search * @param propertyName the name of the property for which the definition should be retrieved. This method will automatically * look for residual definitions, but you can use {@link JcrNodeType#RESIDUAL_ITEM_NAME} to retrieve only the best * residual property definition (if any). * @param values the values * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if * this operation is being done from within internal implementations * @return the best property definition, or <code>null</code> if no property definition allows the property with the supplied * name, type and number of values */ JcrPropertyDefinition findPropertyDefinition( JcrSession session, Name primaryTypeName, Collection<Name> mixinTypeNames, Name propertyName, Value[] values, boolean skipProtected ) { return findPropertyDefinition(session, primaryTypeName, mixinTypeNames, propertyName, values, skipProtected, true); } /** * Searches the supplied primary node type and the mixin node types for a property definition that is the best match for the * given property name, property type, and value. * <p> * This method first attempts to find a single-valued property definition with the supplied property name and * {@link Value#getType() value's property type} in the primary type, skipping any property definitions that are protected. * The property definition is returned if it has a matching type (or has an {@link PropertyType#UNDEFINED undefined property * type}) and the value satisfies the {@link PropertyDefinition#getValueConstraints() definition's constraints}. Otherwise, * the process continues with each of the mixin types, in the order they are named. * </p> * <p> * If no matching property definition could be found (and <code>checkMultiValuedDefinitions</code> parameter is * <code>true</code>), the process is repeated except with multi-valued property definitions with the same name, property * type, and compatible constraints, starting with the primary type and continuing with each mixin type. * </p> * <p> * If no matching property definition could be found, and the process repeats by searching the primary type (and then mixin * types) for single-valued property definitions with a compatible type, where the values can be safely cast to the * definition's property type and still satisfy the definition's constraints. * </p> * <p> * If no matching property definition could be found, the previous step is repeated with multi-valued property definitions. * </p> * <p> * If no matching property definition could be found (and the supplied property name is not the residual name), the whole * process repeats for residual property definitions (e.g., those that are defined with a {@link JcrNodeType#RESIDUAL_NAME "*" * name}). * </p> * <p> * Finally, if no satisfactory property definition could be found, this method returns null. * </p> * * @param session the session in which the constraints are to be checked; may not be null * @param primaryTypeName the name of the primary type; may not be null * @param mixinTypeNames the names of the mixin types; may be null or empty if there are no mixins to include in the search * @param propertyName the name of the property for which the definition should be retrieved. This method will automatically * look for residual definitions, but you can use {@link JcrNodeType#RESIDUAL_ITEM_NAME} to retrieve only the best * residual property definition (if any). * @param values the values * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if * this operation is being done from within internal implementations * @param checkTypeAndConstraints true if the type and constraints of the property definition should be checked, or false * otherwise * @return the best property definition, or <code>null</code> if no property definition allows the property with the supplied * name, type and number of values */ JcrPropertyDefinition findPropertyDefinition( JcrSession session, Name primaryTypeName, Collection<Name> mixinTypeNames, Name propertyName, Value[] values, boolean skipProtected, boolean checkTypeAndConstraints ) { boolean setToEmpty = values == null; int propertyType = values == null || values.length == 0 ? PropertyType.STRING : values[0].getType(); /* * We use this flag to indicate that there was a definition encountered with the same name. If * a named definition (or definitions - for example the same node type could define a LONG and BOOLEAN * version of the same property) is encountered and no match is found for the name, then processing should not * proceed. If processing did proceed, a residual definition might be found and matched. This would * lead to a situation where a node defined a type for a named property, but contained a property with * the same name and the wrong type. */ boolean matchedOnName = false; // Look for a multi-value property definition on the primary type that matches by name and type ... JcrNodeType primaryType = getNodeType(primaryTypeName); if (primaryType != null) { for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) { matchedOnName = true; // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; if (setToEmpty) { if (!definition.isMandatory()) return definition; // Otherwise this definition doesn't work, so continue with the next ... continue; } assert values != null; // We can use the definition if it matches the type and satisfies the constraints ... int type = definition.getRequiredType(); boolean typeMatches = values.length == 0 || type == PropertyType.UNDEFINED || type == propertyType; // Don't check constraints on reference properties if (typeMatches) { if (type == PropertyType.REFERENCE) return definition; if (type == PropertyType.WEAKREFERENCE) return definition; if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE) return definition; if (!checkTypeAndConstraints) return definition; if (definition.satisfiesConstraints(values, session)) return definition; } } if (matchedOnName) { if (values != null && values.length != 0) { // Nothing was found with matching name and type, so look for definitions with // matching name and an undefined or castable type ... // Look for a multi-value property definition on the primary type that matches by name and type ... for (JcrPropertyDefinition definition : primaryType.allMultiValuePropertyDefinitions(propertyName)) { // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; assert definition.getRequiredType() != PropertyType.UNDEFINED; // Don't check constraints on reference properties if (definition.getRequiredType() == PropertyType.REFERENCE && definition.canCastToType(values)) return definition; if (definition.getRequiredType() == PropertyType.WEAKREFERENCE && definition.canCastToType(values)) return definition; if (definition.getRequiredType() == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && definition.canCastToType(values)) return definition; if (!checkTypeAndConstraints) return definition; if (definition.canCastToTypeAndSatisfyConstraints(values, session)) return definition; } } return null; } } // Look for a multi-value property definition on the mixin types that matches by name and type ... List<JcrNodeType> mixinTypes = null; if (mixinTypeNames != null) { mixinTypes = new LinkedList<JcrNodeType>(); for (Name mixinTypeName : mixinTypeNames) { JcrNodeType mixinType = getNodeType(mixinTypeName); if (mixinType == null) continue; mixinTypes.add(mixinType); for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) { matchedOnName = true; // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; if (setToEmpty) { if (!definition.isMandatory()) return definition; // Otherwise this definition doesn't work, so continue with the next ... continue; } assert values != null; // We can use the definition if it matches the type and satisfies the constraints ... int type = definition.getRequiredType(); boolean typeMatches = values.length == 0 || type == PropertyType.UNDEFINED || type == propertyType; // Don't check constraints on reference properties if (typeMatches) { if (type == PropertyType.REFERENCE) return definition; if (type == PropertyType.WEAKREFERENCE) return definition; if (type == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE) return definition; if (!checkTypeAndConstraints) return definition; if (definition.satisfiesConstraints(values, session)) return definition; } } if (matchedOnName) { if (values != null && values.length != 0) { // Nothing was found with matching name and type, so look for definitions with // matching name and an undefined or castable type ... // Look for a multi-value property definition on the mixin type that matches by name and type ... for (JcrPropertyDefinition definition : mixinType.allMultiValuePropertyDefinitions(propertyName)) { // See if the definition allows the value ... if (skipProtected && definition.isProtected()) return null; assert definition.getRequiredType() != PropertyType.UNDEFINED; // Don't check constraints on reference properties if (definition.getRequiredType() == PropertyType.REFERENCE && definition.canCastToType(values)) return definition; if (definition.getRequiredType() == PropertyType.WEAKREFERENCE && definition.canCastToType(values)) return definition; if (definition.getRequiredType() == org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE && definition.canCastToType(values)) return definition; if (!checkTypeAndConstraints) return definition; if (definition.canCastToTypeAndSatisfyConstraints(values, session)) return definition; } } return null; } } } // Nothing was found, so look for residual property definitions ... if (!propertyName.equals(JcrNodeType.RESIDUAL_NAME)) return findPropertyDefinition(session, primaryTypeName, mixinTypeNames, JcrNodeType.RESIDUAL_NAME, values, skipProtected, checkTypeAndConstraints); return null; } /** * Searches the supplied primary and mixin node types for all valid property definitions that match the given property name * and cardinality. * <p> * If no satisfactory property definition could be found, this method returns an empty list. * </p> * * @param typeNamesToCheck the name of the types to check; may not be null * @param propertyName the name of the property for which the definitions should be retrieved * @param typeToCheck the type of definitions to consider (single-valued only, multi-valued only, or all) * @param pendingTypes a list of types that have been created during type registration but not yet registered in the type map * @return a list of all valid property definitions that match the given property name and cardinality */ List<JcrPropertyDefinition> findPropertyDefinitions( List<Name> typeNamesToCheck, Name propertyName, PropertyCardinality typeToCheck, List<JcrNodeType> pendingTypes ) { assert typeNamesToCheck != null; Collection<JcrPropertyDefinition> propDefs = null; List<JcrPropertyDefinition> matchingDefs = new ArrayList<JcrPropertyDefinition>(); // Look for a single-value property definition on the mixin types that matches by name and type ... for (Name typeNameToCheck : typeNamesToCheck) { JcrNodeType typeName = findTypeInMapOrList(typeNameToCheck, pendingTypes); if (typeName == null) continue; switch (typeToCheck) { case SINGLE_VALUED_ONLY: propDefs = typeName.allSingleValuePropertyDefinitions(propertyName); break; case MULTI_VALUED_ONLY: propDefs = typeName.allMultiValuePropertyDefinitions(propertyName); break; case ANY: propDefs = typeName.allPropertyDefinitions(propertyName); break; default: throw new IllegalStateException("Should be unreachable: " + typeToCheck); } if (!propDefs.isEmpty()) matchingDefs.addAll(propDefs); } return matchingDefs; } /** * Determine if the property definitions of the supplied primary type and mixin types allow the property with the supplied * name to be removed. * * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins * to include in the search * @param propertyName the name of the property to be removed; may not be null * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if * this operation is being done from within internal implementations * @return true if at least one child node definition does not require children with the supplied name to exist, or false * otherwise */ boolean canRemoveProperty( Name primaryTypeNameOfParent, List<Name> mixinTypeNamesOfParent, Name propertyName, boolean skipProtected ) { // First look in the primary type ... JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent); if (primaryType != null) { for (JcrPropertyDefinition definition : primaryType.allPropertyDefinitions(propertyName)) { // Skip protected definitions ... if (skipProtected && definition.isProtected()) continue; // If this definition is not mandatory, then we have found that we CAN remove the property ... return !definition.isMandatory(); } } // Then, look in the mixin types ... if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) { for (Name mixinTypeName : mixinTypeNamesOfParent) { JcrNodeType mixinType = getNodeType(mixinTypeName); if (mixinType == null) continue; for (JcrPropertyDefinition definition : mixinType.allPropertyDefinitions(propertyName)) { // Skip protected definitions ... if (skipProtected && definition.isProtected()) continue; // If this definition is not mandatory, then we have found that we CAN remove the property ... return !definition.isMandatory(); } } } // Nothing was found, so look for residual node definitions ... if (!propertyName.equals(JcrNodeType.RESIDUAL_NAME)) return canRemoveProperty(primaryTypeNameOfParent, mixinTypeNamesOfParent, JcrNodeType.RESIDUAL_NAME, skipProtected); return false; } /** * Determine if the node and property definitions of the supplied primary type and mixin types allow the item with the * supplied name to be removed. * * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins * to include in the search * @param itemName the name of the item to be removed; may not be null * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if * this operation is being done from within internal implementations * @return true if at least one child node definition does not require children with the supplied name to exist, or false * otherwise */ boolean canRemoveItem( Name primaryTypeNameOfParent, List<Name> mixinTypeNamesOfParent, Name itemName, boolean skipProtected ) { // First look in the primary type for a matching property definition... JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent); if (primaryType != null) { for (JcrPropertyDefinition definition : primaryType.allPropertyDefinitions(itemName)) { // Skip protected definitions ... if (skipProtected && definition.isProtected()) continue; // If this definition is not mandatory, then we have found that we CAN remove the property ... return !definition.isMandatory(); } } // Then, look in the primary type for a matching child node definition... if (primaryType != null) { for (JcrNodeDefinition definition : primaryType.allChildNodeDefinitions(itemName)) { // Skip protected definitions ... if (skipProtected && definition.isProtected()) continue; // If this definition is not mandatory, then we have found that we CAN remove all children ... return !definition.isMandatory(); } } // Then, look in the mixin types for a matching property definition... if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) { for (Name mixinTypeName : mixinTypeNamesOfParent) { JcrNodeType mixinType = getNodeType(mixinTypeName); if (mixinType == null) continue; for (JcrPropertyDefinition definition : mixinType.allPropertyDefinitions(itemName)) { // Skip protected definitions ... if (skipProtected && definition.isProtected()) continue; // If this definition is not mandatory, then we have found that we CAN remove the property ... return !definition.isMandatory(); } } } // Then, look in the mixin types for a matching child node definition... if (mixinTypeNamesOfParent != null && !mixinTypeNamesOfParent.isEmpty()) { for (Name mixinTypeName : mixinTypeNamesOfParent) { JcrNodeType mixinType = getNodeType(mixinTypeName); if (mixinType == null) continue; for (JcrNodeDefinition definition : mixinType.allChildNodeDefinitions(itemName)) { // Skip protected definitions ... if (skipProtected && definition.isProtected()) continue; // If this definition is not mandatory, then we have found that we CAN remove all children ... return !definition.isMandatory(); } } } // Nothing was found, so look for residual item definitions ... if (!itemName.equals(JcrNodeType.RESIDUAL_NAME)) return canRemoveItem(primaryTypeNameOfParent, mixinTypeNamesOfParent, JcrNodeType.RESIDUAL_NAME, skipProtected); return false; } protected final JcrNodeDefinition findChildNodeDefinitionForUnstructured() { return ntUnstructuredSnsChildDefinition; } private Set<Name> mixinsWithChildNodeDefinitions( Set<Name> mixinTypes ) { if (mixinTypes == null || mixinTypes.isEmpty()) return NONE; if (nodeTypeNamesWithNoChildNodeDefns.containsAll(mixinTypes)) { // None of the mixins has a child node definition. This is usually true of all mixins, so this will happen a lot ... return NONE; } // We know at least one of the mixins defines child node definitions ... if (mixinTypes.size() == 1) return mixinTypes; // This happens pretty infrequently ... Set<Name> result = new HashSet<Name>(); for (Name mixinType : mixinTypes) { if (!nodeTypeNamesWithNoChildNodeDefns.contains(mixinType)) result.add(mixinType); } assert !result.isEmpty(); return result; } private NodeDefinitionSet use( ReusableNodeDefinitionSet defnSet ) { nodeDefinitionSet.set(defnSet); return defnSet; } /** * Searches the supplied primary and mixin node types for all valid child node definitions that match the given child node * name and cardinality. * <p> * If no satisfactory child node definition could be found, this method returns an empty list. * </p> * * @param typeNamesToCheck the name of the types to check; may not be null * @param childNodeName the name of the child node for which the definitions should be retrieved * @param typesToCheck the type of definitions to consider (allows SNS or does not allow SNS) * @param pendingTypes a list of types that have been created during type registration but not yet registered in the type map * @return a list of all valid chlid node definitions that match the given child node name and cardinality */ List<JcrNodeDefinition> findChildNodeDefinitions( List<Name> typeNamesToCheck, Name childNodeName, NodeCardinality typesToCheck, List<JcrNodeType> pendingTypes ) { assert typeNamesToCheck != null; Collection<JcrNodeDefinition> nodeDefs = null; List<JcrNodeDefinition> matchingDefs = new ArrayList<JcrNodeDefinition>(); for (Name typeNameToCheck : typeNamesToCheck) { JcrNodeType typeName = findTypeInMapOrList(typeNameToCheck, pendingTypes); if (typeName == null) continue; switch (typesToCheck) { case NO_SAME_NAME_SIBLINGS: nodeDefs = typeName.allChildNodeDefinitions(childNodeName, false); break; case SAME_NAME_SIBLINGS: nodeDefs = typeName.allChildNodeDefinitions(childNodeName, true); break; case ANY: nodeDefs = typeName.allChildNodeDefinitions(childNodeName); break; } assert nodeDefs != null; for (JcrNodeDefinition definition : nodeDefs) { if (NodeCardinality.NO_SAME_NAME_SIBLINGS == typesToCheck && definition.allowsSameNameSiblings()) continue; matchingDefs.add(definition); } } return matchingDefs; } /** * Determine if the child node definitions of the supplied primary type and mixin types of a parent node allow all of the * children with the supplied name to be removed. * * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins * to include in the search * @param childName the name of the child to be added to the parent; may not be null * @param skipProtected true if this operation is being done from within the public JCR node and property API, or false if * this operation is being done from within internal implementations * @return true if at least one child node definition does not require children with the supplied name to exist, or false * otherwise */ boolean canRemoveAllChildren( Name primaryTypeNameOfParent, Collection<Name> mixinTypeNamesOfParent, Name childName, boolean skipProtected ) { // First look in the primary type ... JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent); if (primaryType != null) { for (JcrNodeDefinition definition : primaryType.allChildNodeDefinitions(childName)) { // Skip protected definitions ... if (skipProtected && definition.isProtected()) continue; // If this definition is not mandatory, then we have found that we CAN remove all children ... return !definition.isMandatory(); } } // Then, look in the mixin types ... if (mixinTypeNamesOfParent != null) { for (Name mixinTypeName : mixinTypeNamesOfParent) { JcrNodeType mixinType = getNodeType(mixinTypeName); if (mixinType == null) continue; for (JcrNodeDefinition definition : mixinType.allChildNodeDefinitions(childName)) { // Skip protected definitions ... if (skipProtected && definition.isProtected()) continue; // If this definition is not mandatory, then we have found that we CAN remove all children ... return !definition.isMandatory(); } } } // Nothing was found, so look for residual node definitions ... if (!childName.equals(JcrNodeType.RESIDUAL_NAME)) return canRemoveAllChildren(primaryTypeNameOfParent, mixinTypeNamesOfParent, JcrNodeType.RESIDUAL_NAME, skipProtected); return false; } /** * Finds the named type in the given collection of types pending registration if it exists, else returns the type definition * from the repository * * @param typeName the name of the type to retrieve * @param pendingList a collection of types that have passed validation but have not yet been committed to the repository * @return the node type with the given name from {@code pendingList} if it exists in the collection or from the * {@link #nodeTypes registered types} if it exists there; may be null */ protected JcrNodeType findTypeInMapOrList( Name typeName, Collection<JcrNodeType> pendingList ) { for (JcrNodeType pendingNodeType : pendingList) { if (pendingNodeType.getInternalName().equals(typeName)) { return pendingNodeType; } } return nodeTypes.get(typeName); } /** * Returns the list of node types for the supertypes defined in the given node type. * * @param nodeType a node type with a non-null array of supertypes * @param pendingTypes the list of types that have been processed in this type batch but not yet committed to the repository's * set of types * @return a list of node types where each element is the node type for the corresponding element of the array of supertype * names * @throws RepositoryException if any of the names in the array of supertype names does not correspond to an * already-registered node type or a node type that is pending registration */ protected List<JcrNodeType> supertypesFor( NodeTypeDefinition nodeType, Collection<JcrNodeType> pendingTypes ) throws RepositoryException { assert nodeType != null; List<JcrNodeType> supertypes = new LinkedList<JcrNodeType>(); boolean isMixin = nodeType.isMixin(); boolean needsPrimaryAncestor = !isMixin; String nodeTypeName = nodeType.getName(); for (String supertypeNameStr : nodeType.getDeclaredSupertypeNames()) { Name supertypeName = nameFactory.create(supertypeNameStr); JcrNodeType supertype = findTypeInMapOrList(supertypeName, pendingTypes); if (supertype == null) { throw new InvalidNodeTypeDefinitionException(JcrI18n.invalidSupertypeName.text(supertypeNameStr, nodeTypeName)); } needsPrimaryAncestor &= supertype.isMixin(); supertypes.add(supertype); } // primary types (other than nt:base) always have at least one ancestor that's a primary type - nt:base if (needsPrimaryAncestor) { Name nodeName = nameFactory.create(nodeTypeName); if (!JcrNtLexicon.BASE.equals(nodeName)) { JcrNodeType ntBase = findTypeInMapOrList(JcrNtLexicon.BASE, pendingTypes); assert ntBase != null; supertypes.add(0, ntBase); } } return supertypes; } /** * Returns the list of subtypes for the given node. * * @param nodeType the node type for which subtypes should be returned; may not be null * @return the subtypes for the node */ final Collection<JcrNodeType> subtypesFor( JcrNodeType nodeType ) { List<JcrNodeType> subtypes = new LinkedList<JcrNodeType>(); for (JcrNodeType type : this.nodeTypes.values()) { if (type.supertypes().contains(nodeType)) { subtypes.add(type); } } return subtypes; } /** * Returns the list of declared subtypes for the given node. * * @param nodeType the node type for which declared subtypes should be returned; may not be null * @return the subtypes for the node */ final Collection<JcrNodeType> declaredSubtypesFor( JcrNodeType nodeType ) { CheckArg.isNotNull(nodeType, "nodeType"); String nodeTypeName = nodeType.getName(); List<JcrNodeType> subtypes = new LinkedList<JcrNodeType>(); for (JcrNodeType type : this.nodeTypes.values()) { if (Arrays.asList(type.getDeclaredSupertypeNames()).contains(nodeTypeName)) { subtypes.add(type); } } return subtypes; } /** * Validates that the supertypes are compatible under ModeShape restrictions. * <p> * ModeShape imposes the following rules on the supertypes of a type: * <ol> * <li>The type must have at least one supertype (unless the type is {@code nt:base}.</li> * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a property ({@code p1} and {@code p2}) with the * same name and cardinality ({@code p1.isMultiple() == p2.isMultiple()}). Note that this does prohibit each {@code t1} and * {@code t2} from having a common supertype (or super-supertype, etc.) that declares a property).</li> * <li>No two supertypes {@code t1} and {@code t2} can declare each declare a child node ({@code n1} and {@code n2}) with the * same name and SNS status ({@code p1.allowsSameNameSiblings() == p2.allowsSameNameSiblings()}). Note that this does prohibit * each {@code t1} and {@code t2} from having a common supertype (or super-supertype, etc.) that declares a child node).</li> * </ol> * </p> * <p> * If any of these rules are violated, a {@link RepositoryException} is thrown. * </p> * * @param supertypes the supertypes of this node type * @throws RepositoryException if any of the rules described above are violated */ private void validateSupertypes( List<JcrNodeType> supertypes ) throws RepositoryException { assert supertypes != null; Map<PropertyDefinitionId, JcrPropertyDefinition> props = new HashMap<PropertyDefinitionId, JcrPropertyDefinition>(); for (JcrNodeType supertype : supertypes) { for (JcrPropertyDefinition property : supertype.propertyDefinitions()) { JcrPropertyDefinition oldProp = props.put(new PropertyDefinitionId(property.getInternalName(), property.getInternalName(), PropertyType.UNDEFINED, property.isMultiple()), property); if (oldProp != null) { String oldPropTypeName = oldProp.getDeclaringNodeType().getName(); String propTypeName = property.getDeclaringNodeType().getName(); if (!oldPropTypeName.equals(propTypeName)) { throw new InvalidNodeTypeDefinitionException(JcrI18n.supertypesConflict.text(oldPropTypeName, propTypeName, "property", property.getName())); } } } } Map<NodeDefinitionId, JcrNodeDefinition> childNodes = new HashMap<NodeDefinitionId, JcrNodeDefinition>(); for (JcrNodeType supertype : supertypes) { for (JcrNodeDefinition childNode : supertype.childNodeDefinitions()) { JcrNodeDefinition oldNode = childNodes.put(new NodeDefinitionId(childNode.getInternalName(), childNode.getInternalName(), new Name[0]), childNode); if (oldNode != null) { String oldNodeTypeName = oldNode.getDeclaringNodeType().getName(); String childNodeTypeName = childNode.getDeclaringNodeType().getName(); if (!oldNodeTypeName.equals(childNodeTypeName)) { throw new InvalidNodeTypeDefinitionException(JcrI18n.supertypesConflict.text(oldNodeTypeName, childNodeTypeName, "child node", childNode.getName())); } } } } } /** * Validates that the given node type definition is valid under the ModeShape and JCR type rules within the given context. * * @param nodeType the node type to attempt to validate * @param supertypes the names of the supertypes of the node type * @param pendingTypes the list of types previously registered in this batch but not yet committed to the repository * @throws RepositoryException if the given node type template is not valid */ protected void validate( JcrNodeType nodeType, List<JcrNodeType> supertypes, List<JcrNodeType> pendingTypes ) throws RepositoryException { validateSupertypes(supertypes); List<Name> supertypeNames = new ArrayList<Name>(supertypes.size()); for (JcrNodeType supertype : supertypes) { supertypeNames.add(supertype.getInternalName()); } boolean foundExact = false; boolean foundResidual = false; boolean foundSNS = false; Name primaryItemName = nodeType.getInternalPrimaryItemName(); for (JcrNodeDefinition node : nodeType.getDeclaredChildNodeDefinitions()) { validateChildNodeDefinition(node, supertypeNames, pendingTypes); if (node.isResidual()) { foundResidual = true; } if (primaryItemName != null && primaryItemName.equals(node.getInternalName())) { foundExact = true; } if (node.allowsSameNameSiblings()) { foundSNS = true; } } for (JcrPropertyDefinition prop : nodeType.getDeclaredPropertyDefinitions()) { validatePropertyDefinition(prop, supertypeNames, pendingTypes); if (prop.isResidual()) { foundResidual = true; } if (primaryItemName != null && primaryItemName.equals(prop.getInternalName())) { if (foundExact) { throw new RepositoryException(JcrI18n.ambiguousPrimaryItemName.text(primaryItemName)); } foundExact = true; } } if (primaryItemName != null && !foundExact && !foundResidual) { throw new RepositoryException(JcrI18n.invalidPrimaryItemName.text(primaryItemName)); } Name internalName = nodeType.getInternalName(); if (isUnorderedCollection(internalName, supertypeNames)) { boolean isVersionable = isVersionable(internalName, supertypeNames); if (isVersionable || foundSNS || nodeType.hasOrderableChildNodes()) { throw new RepositoryException(JcrI18n.invalidUnorderedCollectionType.text(internalName.toString())); } } } /** * Validates that the given child node definition is valid under the ModeShape and JCR type rules within the given context. * <p> * ModeShape considers a child node definition valid if it meets these criteria: * <ol> * <li>Residual child node definitions cannot be mandatory</li> * <li>If the child node is auto-created, it must specify a default primary type name</li> * <li>If the child node overrides an existing child node definition from a supertype, the new definition must be mandatory if * the old definition was mandatory</li> * <li>The child node cannot override an existing child node definition from a supertype if the ancestor definition is * protected</li> * <li>If the child node overrides an existing child node definition from a supertype, the required primary types of the new * definition must be more restrictive than the required primary types of the old definition - that is, the new primary types * must defined such that any type that satisfies all of the required primary types for the new definition must also satisfy * all of the required primary types for the old definition. This requirement is analogous to the requirement that overriding * property definitions have a required type that is always convertible to the required type of the overridden definition.</li> * </ol> * </p> * * @param childNodeDefinition the child node definition to be validated * @param supertypes the names of the supertypes of the node type to which this child node belongs * @param pendingTypes the list of types previously registered in this batch but not yet committed to the repository * @throws RepositoryException if the child node definition is not valid */ private void validateChildNodeDefinition( JcrNodeDefinition childNodeDefinition, List<Name> supertypes, List<JcrNodeType> pendingTypes ) throws RepositoryException { if (childNodeDefinition.isAutoCreated() && !childNodeDefinition.isProtected() && childNodeDefinition.defaultPrimaryTypeName() == null) { throw new InvalidNodeTypeDefinitionException(JcrI18n.autocreatedNodesNeedDefaults.text(childNodeDefinition.getName())); } boolean residual = JcrNodeType.RESIDUAL_ITEM_NAME.equals(childNodeDefinition.getName()); if (childNodeDefinition.isMandatory() && residual) { throw new InvalidNodeTypeDefinitionException( JcrI18n.residualNodeDefinitionsCannotBeMandatory.text(childNodeDefinition.getName())); } if (childNodeDefinition.isAutoCreated() && residual) { throw new InvalidNodeTypeDefinitionException( JcrI18n.residualNodeDefinitionsCannotBeAutoCreated.text(childNodeDefinition.getName())); } Name childNodeName = context.getValueFactories().getNameFactory().create(childNodeDefinition.getName()); childNodeName = childNodeName == null ? JcrNodeType.RESIDUAL_NAME : childNodeName; List<JcrNodeDefinition> childNodesInAncestors = findChildNodeDefinitions(supertypes, childNodeName, NodeCardinality.ANY, pendingTypes); for (JcrNodeDefinition childNodeFromAncestor : childNodesInAncestors) { if (childNodeFromAncestor.isProtected()) { throw new InvalidNodeTypeDefinitionException( JcrI18n.cannotOverrideProtectedDefinition.text(childNodeFromAncestor.getDeclaringNodeType() .getName(), "child node")); } if (childNodeFromAncestor.isMandatory() && !childNodeDefinition.isMandatory()) { throw new InvalidNodeTypeDefinitionException( JcrI18n.cannotMakeMandatoryDefinitionOptional.text(childNodeFromAncestor.getDeclaringNodeType() .getName(), "child node")); } Name[] requiredPrimaryTypeNames = childNodeFromAncestor.requiredPrimaryTypeNames(); for (Name requiredPrimaryTypeName : requiredPrimaryTypeNames) { NodeType requiredPrimaryTypeFromAncestor = findTypeInMapOrList(requiredPrimaryTypeName, pendingTypes); if (requiredPrimaryTypeFromAncestor == null) { I18n msg = JcrI18n.couldNotFindDefinitionOfRequiredPrimaryType; throw new InvalidNodeTypeDefinitionException(msg.text(requiredPrimaryTypeName, childNodeDefinition.getName(), childNodeDefinition.getDeclaringNodeType())); } boolean found = false; for (Name name : childNodeDefinition.requiredPrimaryTypeNames()) { JcrNodeType childNodePrimaryType = findTypeInMapOrList(name, pendingTypes); if (childNodePrimaryType != null && childNodePrimaryType.isNodeType(requiredPrimaryTypeFromAncestor.getName())) { found = true; break; } } // Allow side-by-side definitions of residual child nodes per JCR 1.0.1 spec 6.7.8 if (!found && !residual) { I18n msg = JcrI18n.cannotRedefineChildNodeWithIncompatibleDefinition; throw new InvalidNodeTypeDefinitionException(msg.text(childNodeName, requiredPrimaryTypeFromAncestor.getName(), childNodeDefinition.getDeclaringNodeType())); } } } } /** * Validates that the given property definition is valid under the ModeShape and JCR type rules within the given context. * <p> * ModeShape considers a property definition valid if it meets these criteria: * <ol> * <li>Residual properties cannot be mandatory</li> * <li>If the property is auto-created, it must specify a default value</li> * <li>If the property is single-valued, it can only specify a single default value</li> * <li>If the property overrides an existing property definition from a supertype, the new definition must be mandatory if the * old definition was mandatory</li> * <li>The property cannot override an existing property definition from a supertype if the ancestor definition is protected</li> * <li>If the property overrides an existing property definition from a supertype, the new definition must have the same * required type as the old definition or a required type that can ALWAYS be cast to the required type of the ancestor (see * section 3.6.4 of the JCR 2.0 specification)</li> * </ol> * Note that an empty set of properties would meet the criteria above. * </p> * * @param propertyDefinition the property definition to be validated * @param supertypes the names of the supertypes of the node type to which this property belongs * @param pendingTypes the list of types previously registered in this batch but not yet committed to the repository * @throws RepositoryException if the property definition is not valid */ private void validatePropertyDefinition( JcrPropertyDefinition propertyDefinition, List<Name> supertypes, List<JcrNodeType> pendingTypes ) throws RepositoryException { assert propertyDefinition != null; assert supertypes != null; assert pendingTypes != null; boolean residual = JcrNodeType.RESIDUAL_ITEM_NAME.equals(propertyDefinition.getName()); if (propertyDefinition.isMandatory() && !propertyDefinition.isProtected() && residual) { throw new InvalidNodeTypeDefinitionException( JcrI18n.residualPropertyDefinitionsCannotBeMandatory.text(propertyDefinition.getName())); } if (propertyDefinition.isAutoCreated() && residual) { throw new InvalidNodeTypeDefinitionException( JcrI18n.residualPropertyDefinitionsCannotBeAutoCreated.text(propertyDefinition.getName())); } Value[] defaultValues = propertyDefinition.getDefaultValues(); if (propertyDefinition.isAutoCreated() && !propertyDefinition.isProtected() && (defaultValues == null || defaultValues.length == 0)) { throw new InvalidNodeTypeDefinitionException( JcrI18n.autocreatedPropertyNeedsDefault.text(propertyDefinition.getName(), propertyDefinition.getDeclaringNodeType() .getName())); } if (!propertyDefinition.isMultiple() && (defaultValues != null && defaultValues.length > 1)) { throw new InvalidNodeTypeDefinitionException( JcrI18n.singleValuedPropertyNeedsSingleValuedDefault.text(propertyDefinition.getName(), propertyDefinition.getDeclaringNodeType() .getName())); } Name propName = context.getValueFactories().getNameFactory().create(propertyDefinition.getName()); propName = propName == null ? JcrNodeType.RESIDUAL_NAME : propName; List<JcrPropertyDefinition> propertyDefinitionsFromAncestors = findPropertyDefinitions(supertypes, propName, propertyDefinition.isMultiple() ? PropertyCardinality.MULTI_VALUED_ONLY : PropertyCardinality.SINGLE_VALUED_ONLY, pendingTypes); for (JcrPropertyDefinition propertyDefinitionFromAncestor : propertyDefinitionsFromAncestors) { if (propertyDefinitionFromAncestor.isProtected()) { throw new InvalidNodeTypeDefinitionException( JcrI18n.cannotOverrideProtectedDefinition.text(propertyDefinitionFromAncestor.getDeclaringNodeType() .getName(), "property")); } if (propertyDefinitionFromAncestor.isMandatory() && !propertyDefinition.isMandatory()) { throw new InvalidNodeTypeDefinitionException( JcrI18n.cannotMakeMandatoryDefinitionOptional.text(propertyDefinitionFromAncestor.getDeclaringNodeType() .getName(), "property")); } if (!propertyDefinition.isAsOrMoreConstrainedThan(propertyDefinitionFromAncestor, context)) { throw new InvalidNodeTypeDefinitionException( JcrI18n.constraintsChangedInSubtype.text(propName, propertyDefinitionFromAncestor.getDeclaringNodeType() .getName())); } if (!isAlwaysSafeConversion(propertyDefinition.getRequiredType(), propertyDefinitionFromAncestor.getRequiredType())) { throw new InvalidNodeTypeDefinitionException( JcrI18n.cannotRedefineProperty.text(propName, org.modeshape.jcr.api.PropertyType.nameFromValue(propertyDefinition.getRequiredType()), propertyDefinitionFromAncestor.getDeclaringNodeType() .getName(), org.modeshape.jcr.api.PropertyType.nameFromValue(propertyDefinitionFromAncestor.getRequiredType()))); } } } /** * Returns whether it is always possible to convert a value with JCR property type {@code fromType} to {@code toType}. * <p> * This method is based on the conversions which can never throw an exception in the chart in section 3.6.4 of the JCR 2.0 * specification. * </p> * * @param fromType the type to be converted from * @param toType the type to convert to * @return true if any value with type {@code fromType} can be converted to a type of {@code toType} without a * {@link ValueFormatException} being thrown. * @see PropertyType */ private boolean isAlwaysSafeConversion( int fromType, int toType ) { if (fromType == toType) return true; switch (toType) { case PropertyType.BOOLEAN: return fromType == PropertyType.BINARY || fromType == PropertyType.STRING; case PropertyType.DATE: return fromType == PropertyType.DOUBLE || fromType == PropertyType.LONG; case PropertyType.DOUBLE: // Conversion from DATE could result in out-of-range value return fromType == PropertyType.LONG; case PropertyType.LONG: // Conversion from DATE could result in out-of-range value return fromType == PropertyType.DOUBLE; case PropertyType.PATH: return fromType == PropertyType.NAME; // Values of any type MAY fail when converting to these types case PropertyType.NAME: case PropertyType.REFERENCE: case PropertyType.WEAKREFERENCE: case org.modeshape.jcr.api.PropertyType.SIMPLE_REFERENCE: return false; // Any type can be converted to these types case PropertyType.BINARY: case PropertyType.STRING: case PropertyType.UNDEFINED: return true; default: throw new IllegalStateException("Unexpected state: " + toType); } } @Override public String toString() { return getAllNodeTypes().toString(); } /** * Return the {@link NodeDefinitionSet set of child node definitions} for a parent's primary node type and mixin node types. * The resulting object can be used to find the best child node definition for a new child with the given name and primary * primary node type name. The algorithm used will prefer child node definitions that allow same name siblings in an attempt * to delay as much as possible the (potentially very expensive) determination of whether there are existing children with the * same name. * <p> * This method also uses a thread-based cache so that sequential calls to add children under the same parent node will be * noticeably faster. * * @param primaryTypeNameOfParent the name of the primary type for the parent node; may not be null * @param mixinTypeNamesOfParent the names of the mixin types for the parent node; may be null or empty if there are no mixins * to include in the search * @return the set of child node definitions that can be used to find an appropriate child node definition for one or more * children under the same parent; never null */ NodeDefinitionSet findChildNodeDefinitions( Name primaryTypeNameOfParent, Set<Name> mixinTypeNamesOfParent ) { // See if this is the same parent primaryType & mixins that we just used. This is often the case for a session // adding multiple children to the same parent ... ReusableNodeDefinitionSet lastDefnSet = nodeDefinitionSet.get(); if (lastDefnSet.appliesTo(this, primaryTypeNameOfParent, mixinTypeNamesOfParent)) return lastDefnSet; Set<Name> mixinsWithChildDefns = mixinsWithChildNodeDefinitions(mixinTypeNamesOfParent); // If this is one of the special built-in cases ... for (ReusableNodeDefinitionSet defnSet : standardNodeDefinitionSets) { if (defnSet.appliesTo(this, primaryTypeNameOfParent, mixinsWithChildDefns)) { return use(defnSet); } } // Go through the primary type ... JcrNodeType primaryType = getNodeType(primaryTypeNameOfParent); assert primaryType != null; Collection<JcrNodeDefinition> defns = primaryType.allChildNodeDefinitions(); if (mixinsWithChildDefns.isEmpty()) { if (defns.isEmpty()) { // No child node definitions ... return use(new NoChildrenNodeDefinitionSet(primaryTypeNameOfParent, mixinsWithChildDefns)); } if (defns.size() == 1) { JcrNodeDefinition defn = defns.iterator().next(); return use(new SingleNodeDefinitionSet(primaryTypeNameOfParent, mixinsWithChildDefns, defn)); } // There are multiple child node definitions in the primary type, and no mixins with child node defns ... return use(new MultipleNodeDefinitionSet(primaryTypeNameOfParent, null)); } // There is a primary type and at least one mixin with child node definitions ... return use(new MultipleNodeDefinitionSet(primaryTypeNameOfParent, mixinsWithChildDefns)); } /** * A set of child node definitions under a parent with a specific primary type and optional mixin types. * * @author Randall Hauch (rhauch@redhat.com) */ public static interface NodeDefinitionSet { /** * Find the best child node definition for creating a new child with the given name and primary type. If none of the * parent's child node definitions allow a child with the given name and type, then null is returned and the caller can * call {@link #determineReasonForMismatch} for an appropriate exception. * * @param childName the name of the proposed child; may be null if the child name is to be determined by the definition * @param childPrimaryType the primary type of the proposed child; may be null if the default type is to be used * @param skipProtected true if this method should not consider any of the parent's child node definitions that are * protected, or false if it should consider all child node definitions * @param siblingCounter a function that this method can call to determine how many siblings with the child's name already * exist under the parent * @return the child node definition for the child, or null if one could not be found */ JcrNodeDefinition findBestDefinitionForChild( Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter ); /** * Method that can be called after {@link #findBestDefinitionForChild(Name, Name, boolean, SiblingCounter)} returns null * to throw an appropriate exception for why each of the parent's child node definition are not applicable for the * proposed child. * * @param childName the name of the proposed child; may be null if the child name is to be determined by the definition * @param childPrimaryType the primary type of the proposed child; may be null if the default type is to be used * @param skipProtected true if this method should not consider any of the parent's child node definitions that are * protected, or false if it should consider all child node definitions * @param siblingCounter a function that this method can call to determine how many siblings with the child's name already * exist under the parent * @param parentPrimaryType the primary type of the parent node; may not be null * @param parentMixinTypes the mixin types of the parent node * @param parentPath * @param workspaceName * @param repositoryName * @param context * @throws ConstraintViolationException * @throws ItemExistsException */ void determineReasonForMismatch( Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter, Name parentPrimaryType, Set<Name> parentMixinTypes, Path parentPath, String workspaceName, String repositoryName, ExecutionContext context ) throws ConstraintViolationException, ItemExistsException; } /** * This class is used to build up a useful error message that includes why each of the child node definitions did not match. * When this class is used, we already know that no match was found and the algorithm is repeated with an instance of this * class to capture the reasons why each child node definition could not be used. So this classIt is fairly expensive, but * we're already in a failed attempt that will result in an exception. */ private final class MatchResults { private final String childName; private final String childPrimaryType; private final String parentPrimaryType; private final String parentMixinTypes; private final String parentPath; private final String workspaceName; private final String repositoryName; private final StringBuilder reasons = new StringBuilder(); private boolean constraintViolation = true; private final Set<JcrNodeDefinition> recorded = new HashSet<>(); protected MatchResults( Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter, Name parentPrimaryType, Set<Name> parentMixinTypes, Path parentPath, String workspaceName, String repositoryName, ExecutionContext context ) { StringFactory strings = context.getValueFactories().getStringFactory(); this.childName = strings.create(childName); this.childPrimaryType = strings.create(childPrimaryType); this.parentPrimaryType = strings.create(parentPrimaryType); StringBuilder parentMixinTypesStr = new StringBuilder("{"); for (Name mixin : parentMixinTypes) { if (parentMixinTypesStr.length() > 1) parentMixinTypesStr.append(','); parentMixinTypesStr.append(strings.create(mixin)); } parentMixinTypesStr.append('}'); this.parentMixinTypes = parentMixinTypesStr.toString(); this.parentPath = strings.create(parentPath); this.workspaceName = workspaceName; this.repositoryName = repositoryName; } protected String readable( JcrNodeDefinition defn ) { return defn.toString(); } public void nameNotSatisfied( JcrNodeDefinition defn ) { if (!recorded.add(defn)) return; reasons.append("\n "); reasons.append(JcrI18n.childNameDoesNotSatisfyParentChildNodeDefinition.text(childName, readable(defn))); } public void requiredTypesNotSatisfied( JcrNodeDefinition defn ) { if (!recorded.add(defn)) return; reasons.append("\n "); reasons.append(JcrI18n.childPrimaryTypeDoesNotSatisfyParentChildNodeDefinition.text(childPrimaryType, readable(defn))); } public void noSameNameSiblingsAllowed( JcrNodeDefinition defn ) { if (!recorded.add(defn)) return; constraintViolation = false; reasons.append("\n "); reasons.append(JcrI18n.parentChildNodeDefinitionDoesNotAllowSameNameSiblings.text(childName, readable(defn))); } public void noProtectedDefinitionsAllowed( JcrNodeDefinition defn ) { if (!recorded.add(defn)) return; reasons.append("\n "); reasons.append(JcrI18n.parentChildNodeDefinitionIsProtected.text(readable(defn))); } public void throwFailure() throws ConstraintViolationException, ItemExistsException { String msg = JcrI18n.unableToAddChildUnderParent.text(childName, childPrimaryType, parentPath, parentPrimaryType, parentMixinTypes, workspaceName, repositoryName, reasons); if (constraintViolation) throw new ConstraintViolationException(msg); throw new ItemExistsException(msg); } } /** * An internal extension to NodeDefinitionSet that adds the #appliesTo method so that the NodeDefinitionSet can be cached and * reused. */ private static interface ReusableNodeDefinitionSet extends NodeDefinitionSet { boolean appliesTo( NodeTypes nodeTypes, Name primaryType, Set<Name> mixinTypes ); } /** * Basic implementation of {@link ReusableNodeDefinitionSet} that provides several utility methods for finding the best child * node definition and for determining the reasons why child node definitions did not match. */ private abstract class AbstractNodeDefinitionSet implements ReusableNodeDefinitionSet { protected final Name primaryType; protected final Set<Name> mixinTypes; protected AbstractNodeDefinitionSet( Name primaryType, Set<Name> mixinTypes ) { this.primaryType = primaryType; this.mixinTypes = mixinTypes; } @SuppressWarnings( "synthetic-access" ) @Override public boolean appliesTo( NodeTypes nodeTypes, Name primaryType, Set<Name> mixinTypes ) { // If the 'NodeType' instance is different, at least one node type has changed, so we should't reuse this object ... if (NodeTypes.this != nodeTypes) return false; if (!this.primaryType.equals(primaryType)) return false; if (this.mixinTypes == null || this.mixinTypes.isEmpty()) { if (mixinTypes == null || mixinTypes.isEmpty()) return true; if (nodeTypeNamesWithNoChildNodeDefns.containsAll(mixinTypes)) return true; } return false; } protected JcrNodeDefinition findBest( JcrNodeDefinition defn, Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter ) { return childDefinitionSatisfies(defn, childName, childPrimaryType, skipProtected, siblingCounter, null); } protected void reasonNotMatched( JcrNodeDefinition defn, Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter, Name parentPrimaryType, Set<Name> parentMixinTypes, Path parentPath, String workspaceName, String repositoryName, ExecutionContext context ) throws ConstraintViolationException, ItemExistsException { MatchResults builder = new MatchResults(childName, childPrimaryType, skipProtected, siblingCounter, parentPrimaryType, parentMixinTypes, parentPath, workspaceName, repositoryName, context); childDefinitionSatisfies(defn, childName, childPrimaryType, skipProtected, siblingCounter, builder); builder.throwFailure(); } protected JcrNodeDefinition findBest( JcrNodeType primaryType, JcrNodeType[] additionalTypes, Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter ) { return childDefinitionSatisfies(primaryType, additionalTypes, childName, childPrimaryType, skipProtected, siblingCounter, null); } protected void reasonNotMatched( JcrNodeType primaryType, JcrNodeType[] additionalTypes, Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter, Name parentPrimaryType, Set<Name> parentMixinTypes, Path parentPath, String workspaceName, String repositoryName, ExecutionContext context ) throws ConstraintViolationException, ItemExistsException { MatchResults builder = new MatchResults(childName, childPrimaryType, skipProtected, siblingCounter, parentPrimaryType, parentMixinTypes, parentPath, workspaceName, repositoryName, context); childDefinitionSatisfies(primaryType, additionalTypes, childName, childPrimaryType, skipProtected, siblingCounter, builder); builder.throwFailure(); } /** * Figure out whether a child with the given name and primary type can be added under a parent with only the given child * node definition. This logic is much more streamlined than * {@link #childDefinitionSatisfies(JcrNodeType, JcrNodeType[], Name, Name, boolean, SiblingCounter, MatchResults)}, and * it's worth having because such cases are very prevalent. * * @param defn the parent's sole child node definition; may not be null * @param childName the name of the proposed child; may be null if the child name is to be determined by the definition * @param childPrimaryType the primary type of the proposed child; may be null if the default type is to be used * @param skipProtected true if this method should not consider any of the parent's child node definitions that are * protected, or false if it should consider all child node definitions * @param siblingCounter a function that this method can call to determine how many siblings with the child's name already * exist under the parent * @param matches the mismatch results object that, if the child definition could not be used for this child, records why; * may be null if only attempting to find a matching definition (pass 1) * @return the matching node definition, or null if the definition does not match * @see #childDefinitionSatisfies(JcrNodeType, JcrNodeType[], Name, Name, boolean, SiblingCounter, MatchResults) */ private JcrNodeDefinition childDefinitionSatisfies( JcrNodeDefinition defn, Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter, MatchResults matches ) { if (defn.isProtected() && skipProtected) { if (matches != null) matches.noProtectedDefinitionsAllowed(defn); return null; } // The node's name must satisfy the definition's name requirements ... if (childName != null && !defn.isResidual()) { // A specific name is required ... if (!defn.getInternalName().equals(childName)) { // The child name doesn't match the definition name, so this definition won't work ... if (matches != null) matches.nameNotSatisfied(defn); return null; } } // The node's type must satisfy the definition's type requirements ... if (childPrimaryType != null && defn.hasRequiredPrimaryTypes()) { JcrNodeType childPrimaryTypeObj = getNodeType(childPrimaryType); if (!defn.allowsChildWithType(childPrimaryTypeObj)) { // The child's type doesn't satisfy the definition's requirements, so this definition won't work ... if (matches != null) matches.requiredTypesNotSatisfied(defn); return null; } } if (!defn.allowsSameNameSiblings()) { // We have to know how many same-name-siblings there already are in the parent ... if (siblingCounter.countSiblingsNamed(childName) > 0) { // The child's type doesn't allow SNS, and there is already at least one child with that name ... if (matches != null) matches.noSameNameSiblingsAllowed(defn); return null; } } // The definition matches all of the requirements ... return defn; } /** * Figure out whether a child with the given name and primary type can be added under a parent with a primary type and * additional mixin types that have child node definitions. This logic is more complicated than the more efficient * {@link #childDefinitionSatisfies(JcrNodeDefinition, Name, Name, boolean, SiblingCounter, MatchResults)}. * * @param primaryType the parent's primary node type; may not be null * @param additionalTypes the parent's mixin node types; may be null, but generally should not be empty * @param childName the name of the proposed child; may be null if the child name is to be determined by the definition * @param childPrimaryType the primary type of the proposed child; may be null if the default type is to be used * @param skipProtected true if this method should not consider any of the parent's child node definitions that are * protected, or false if it should consider all child node definitions * @param siblingCounter a function that this method can call to determine how many siblings with the child's name already * exist under the parent * @param matches the mismatch results object that, if the child definition could not be used for this child, records why; * may be null if only attempting to find a matching definition (pass 1) * @return the matching node definition, or null if the definition does not match * @see #childDefinitionSatisfies(JcrNodeDefinition, Name, Name, boolean, SiblingCounter, MatchResults) */ private JcrNodeDefinition childDefinitionSatisfies( JcrNodeType primaryType, JcrNodeType[] additionalTypes, Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter, MatchResults matches ) { assert primaryType != null; JcrNodeType childType = childPrimaryType != null ? getNodeType(childPrimaryType) : null; JcrNodeDefinition defn = null; // First check for child node defns that match by name, type, and allow SNS ... defn = childDefinitionSatisfies(primaryType, childType, childName, skipProtected, true, matches); if (defn != null) return defn; if (additionalTypes != null) { for (JcrNodeType additionalType : additionalTypes) { defn = childDefinitionSatisfies(additionalType, childType, childName, skipProtected, true, matches); if (defn != null) return defn; } } // Then check for child node defns that match by name and type but that do not allow SNS ... defn = childDefinitionSatisfies(primaryType, childType, childName, skipProtected, false, matches); if (defn == null && additionalTypes != null) { for (JcrNodeType additionalType : additionalTypes) { defn = childDefinitionSatisfies(additionalType, childType, childName, skipProtected, false, matches); if (defn != null) break; } } if (defn != null) { // We found a child node defn that matches exactly by at least name but does not allow SNS. // Even though it might be expensive to count the existing children, a non-SNS child node definition that // matches the same name as our new child generally implies we should use that child node defn ... if (siblingCounter.countSiblingsNamed(childName) == 0) { // There are no existing siblings with this name, so we're good ... return defn; } // There are existing siblings, but the name matches a non-SNS child node definition. if (matches != null) matches.noSameNameSiblingsAllowed(defn); // And see what else is available ... } // Check for residual child node defns that match by type and allow SNS ... defn = childDefinitionSatisfies(primaryType, childType, JcrNodeType.RESIDUAL_NAME, skipProtected, true, matches); if (defn != null) return defn; if (additionalTypes != null) { for (JcrNodeType additionalType : additionalTypes) { defn = childDefinitionSatisfies(additionalType, childType, JcrNodeType.RESIDUAL_NAME, skipProtected, true, matches); if (defn != null) return defn; } } if (childName != null) { // No child definitions were found that allow SNS, so see how many existing same-name-siblings there are ... if (siblingCounter.countSiblingsNamed(childName) > 0) { // This child will require a SNS but we didn't find any child node definitions that matched ... if (matches != null) { // Go through those child defns to mark with a reason ... for (JcrNodeDefinition childDefn : primaryType.allChildNodeDefinitions()) { if (!childDefn.allowsSameNameSiblings()) matches.nameNotSatisfied(childDefn); if (!childDefn.isResidual() && !childDefn.getName().equals(childName)) { // Allows SNS, not residual, doesn't match name ... matches.nameNotSatisfied(childDefn); } else { // Allows SNS, is residual (matching any name) or matches name; // if the defn was not already used, it has to be because the type is wrong ... matches.requiredTypesNotSatisfied(childDefn); } } } return null; } // There are no siblings with same name, so we can look for child defns that match by name, type and no SNS ... defn = childDefinitionSatisfies(primaryType, childType, childName, skipProtected, false, matches); if (defn != null) return defn; if (additionalTypes != null) { for (JcrNodeType additionalType : additionalTypes) { defn = childDefinitionSatisfies(additionalType, childType, childName, skipProtected, false, matches); if (defn != null) return defn; } } } // Check for residual child node defns that match by type and no SNS ... defn = childDefinitionSatisfies(primaryType, childType, JcrNodeType.RESIDUAL_NAME, skipProtected, false, matches); if (defn != null) return defn; if (additionalTypes != null) { for (JcrNodeType additionalType : additionalTypes) { defn = childDefinitionSatisfies(additionalType, childType, JcrNodeType.RESIDUAL_NAME, skipProtected, false, matches); if (defn != null) return defn; } } // None were matched ... return null; } /** * Figure out whether a child with the given name and primary type can be added under a parent with the given node type. * Note that this is called from within * {@link #childDefinitionSatisfies(JcrNodeType, JcrNodeType[], Name, Name, boolean, SiblingCounter, MatchResults)}. * * @param type the parent node type; may not be null * @param childPrimaryType the primary type for the child; may be null if the primary type is not known and would be * determined by the matching child node definition's default type * @param childName the name of the proposed child; may be null if the child name is to be determined by the definition * @param skipProtected true if this method should not consider any of the parent's child node definitions that are * protected, or false if it should consider all child node definitions * @param allowSns true if this method should consider only those child node definitions that allow SNS, or false * otherwise * @param matches the mismatch results object that, if the child definition could not be used for this child, records why; * may be null if only attempting to find a matching definition (pass 1) * @return the matching node definition, or null if the definition does not match * @see #findBest(JcrNodeType, JcrNodeType[], Name, Name, boolean, SiblingCounter) */ private JcrNodeDefinition childDefinitionSatisfies( JcrNodeType type, JcrNodeType childPrimaryType, Name childName, boolean skipProtected, boolean allowSns, MatchResults matches ) { if (childName != null) { // Either a residual or a name ... // See if the primary type has any child node defns that match by name and allow/disallow SNS ... for (JcrNodeDefinition defn : type.allChildNodeDefinitions(childName, allowSns)) { // Skip protected definitions ... if (skipProtected && defn.isProtected()) { if (matches != null) matches.noProtectedDefinitionsAllowed(defn); continue; } // See if the definition allows a child with the supplied primary type ... if (defn.allowsChildWithType(childPrimaryType)) return defn; // Otherwise, the child's primary type does not satisfy the required types ... if (matches != null) matches.requiredTypesNotSatisfied(defn); } } else { // There is no name, so look thru all non-residual child node defns that have a matching type ... for (JcrNodeDefinition defn : type.allChildNodeDefinitions()) { if (defn.isResidual()) continue; if (!allowSns && !defn.allowsSameNameSiblings()) continue; // Skip protected definitions ... if (skipProtected && defn.isProtected()) { if (matches != null) matches.noProtectedDefinitionsAllowed(defn); continue; } if (defn.allowsChildWithType(childPrimaryType)) { // We found one that matched the type return defn; } if (matches != null) matches.requiredTypesNotSatisfied(defn); } } return null; } } private class SingleNodeDefinitionSet extends AbstractNodeDefinitionSet { private final JcrNodeDefinition defn; protected SingleNodeDefinitionSet( Name primaryType, Set<Name> mixinTypes, JcrNodeDefinition defn ) { super(primaryType, mixinTypes); this.defn = defn; } @Override public JcrNodeDefinition findBestDefinitionForChild( Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter ) { return findBest(defn, childName, childPrimaryType, skipProtected, siblingCounter); } @Override public void determineReasonForMismatch( Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter, Name parentPrimaryType, Set<Name> parentMixinTypes, Path parentPath, String workspaceName, String repositoryName, ExecutionContext context ) throws ConstraintViolationException, ItemExistsException { reasonNotMatched(defn, childName, childPrimaryType, skipProtected, siblingCounter, parentPrimaryType, parentMixinTypes, parentPath, workspaceName, repositoryName, context); } } private class MultipleNodeDefinitionSet extends AbstractNodeDefinitionSet { private final JcrNodeType nodeType; private final JcrNodeType[] additionalTypes; protected MultipleNodeDefinitionSet( Name primaryType, Set<Name> mixinTypes ) { super(primaryType, mixinTypes); this.nodeType = getNodeType(primaryType); if (mixinTypes == null || mixinTypes.isEmpty()) { this.additionalTypes = null; } else { this.additionalTypes = new JcrNodeType[mixinTypes.size()]; int index = -1; for (Name mixinType : mixinTypes) { this.additionalTypes[++index] = getNodeType(mixinType); } } } @Override public JcrNodeDefinition findBestDefinitionForChild( Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter ) { return findBest(nodeType, additionalTypes, childName, childPrimaryType, skipProtected, siblingCounter); } @Override public void determineReasonForMismatch( Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter, Name parentPrimaryType, Set<Name> parentMixinTypes, Path parentPath, String workspaceName, String repositoryName, ExecutionContext context ) throws ConstraintViolationException, ItemExistsException { reasonNotMatched(nodeType, additionalTypes, childName, childPrimaryType, skipProtected, siblingCounter, parentPrimaryType, parentMixinTypes, parentPath, workspaceName, repositoryName, context); } } /** * A {@link NodeDefinitionSet} implementation that never applies any primary type and mixin types. */ private final class NoChildrenNodeDefinitionSet extends AbstractNodeDefinitionSet { protected NoChildrenNodeDefinitionSet( Name primaryType, Set<Name> mixinTypes ) { super(primaryType, mixinTypes); } @Override public JcrNodeDefinition findBestDefinitionForChild( Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter ) { return null; } @Override public void determineReasonForMismatch( Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter, Name parentPrimaryType, Set<Name> parentMixinTypes, Path parentPath, String workspaceName, String repositoryName, ExecutionContext context ) throws ConstraintViolationException { StringFactory strings = context.getValueFactories().getStringFactory(); String parentType = strings.create(parentPrimaryType); StringBuilder parentMixinTypesStr = new StringBuilder('{'); for (Name mixin : parentMixinTypes) { if (parentMixinTypesStr.length() > 1) parentMixinTypesStr.append(','); parentMixinTypesStr.append(strings.create(mixin)); } parentMixinTypesStr.append('}'); throw new ConstraintViolationException(JcrI18n.noChildNodeDefinitions.text(parentPath, parentType, parentMixinTypesStr)); } } /** * A {@link NodeDefinitionSet} implementation that never applies any primary type and mixin types. */ private static final class EmptyNodeDefinitionSet implements ReusableNodeDefinitionSet { protected EmptyNodeDefinitionSet() { } @Override public boolean appliesTo( NodeTypes nodeTypes, Name primaryType, Set<Name> mixinTypes ) { return false; } @Override public JcrNodeDefinition findBestDefinitionForChild( Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter ) { return null; } @Override public void determineReasonForMismatch( Name childName, Name childPrimaryType, boolean skipProtected, SiblingCounter siblingCounter, Name parentPrimaryType, Set<Name> parentMixinTypes, Path parentPath, String workspaceName, String repositoryName, ExecutionContext context ) { assert false : "This method should never be called"; } } }