/* * 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.Collections; import java.util.HashSet; import java.util.Set; import javax.jcr.nodetype.NodeDefinition; import org.modeshape.common.annotation.Immutable; import org.modeshape.jcr.cache.NodeKey; import org.modeshape.jcr.value.Name; import org.modeshape.jcr.value.ValueFactory; /** * ModeShape implementation of the {@link NodeDefinition} class. */ @Immutable class JcrNodeDefinition extends JcrItemDefinition implements NodeDefinition { /** @see NodeDefinition#allowsSameNameSiblings() */ private final boolean allowsSameNameSiblings; /** * The name of the default primary type (if any). The name is used instead of the raw node type to allow circular references a * la <code>nt:unstructured</code>. */ private final Name defaultPrimaryTypeName; private final Name[] requiredPrimaryTypeNames; private final Set<Name> requiredPrimaryTypeNameSet; /** A durable identifier for this node definition. */ private final NodeDefinitionId id; private final NodeKey key; /** Link to the repository node type manager */ private final RepositoryNodeTypeManager nodeTypeManager; JcrNodeDefinition( ExecutionContext context, JcrNodeType declaringNodeType, NodeKey prototypeKey, Name name, int onParentVersion, boolean autoCreated, boolean mandatory, boolean protectedItem, boolean allowsSameNameSiblings, Name defaultPrimaryTypeName, Name[] requiredPrimaryTypeNames ) { this(context, null, declaringNodeType, prototypeKey, name, onParentVersion, autoCreated, mandatory, protectedItem, allowsSameNameSiblings, defaultPrimaryTypeName, requiredPrimaryTypeNames); } JcrNodeDefinition( ExecutionContext context, RepositoryNodeTypeManager nodeTypeManager, JcrNodeType declaringNodeType, NodeKey prototypeKey, Name name, int onParentVersion, boolean autoCreated, boolean mandatory, boolean protectedItem, boolean allowsSameNameSiblings, Name defaultPrimaryTypeName, Name[] requiredPrimaryTypeNames ) { super(context, declaringNodeType, name, onParentVersion, autoCreated, mandatory, protectedItem); this.nodeTypeManager = nodeTypeManager; this.allowsSameNameSiblings = allowsSameNameSiblings; this.defaultPrimaryTypeName = defaultPrimaryTypeName; this.requiredPrimaryTypeNames = requiredPrimaryTypeNames; this.id = this.declaringNodeType == null ? null : new NodeDefinitionId(this.declaringNodeType.getInternalName(), this.name, this.requiredPrimaryTypeNames); this.key = this.id == null ? prototypeKey : prototypeKey.withId("/jcr:system/jcr:nodeTypes/" + this.id.getString()); // Cache the set of required primary type names ... Set<Name> requiredPrimaryTypeNameSet = new HashSet<Name>(); for (Name requiredTypeName : this.requiredPrimaryTypeNames) { requiredPrimaryTypeNameSet.add(requiredTypeName); } this.requiredPrimaryTypeNameSet = Collections.unmodifiableSet(requiredPrimaryTypeNameSet); } private final String string( Name name ) { if (name == null) return null; if (this.context == null) return name.getString(); return name.getString(context.getNamespaceRegistry()); } @Override final NodeKey key() { return key; } /** * Get the durable identifier for this node definition. * * @return the node definition ID; never null */ public NodeDefinitionId getId() { return id; } @Override public boolean allowsSameNameSiblings() { return allowsSameNameSiblings; } /** * Get whether this child node definition has restrictions on the primary type of the children. * * @return true if there is 1 or more required primary types, or false if there are none */ public boolean hasRequiredPrimaryTypes() { return id.hasRequiredPrimaryTypes(); } /** * @return the name of the default primary type for this definition; may be null */ final Name defaultPrimaryTypeName() { return defaultPrimaryTypeName; } @Override public JcrNodeType getDefaultPrimaryType() { // It is valid for this field to be null. if (defaultPrimaryTypeName == null) { return null; } return nodeTypeManager.getNodeTypes().getNodeType(defaultPrimaryTypeName); } @Override public JcrNodeType[] getRequiredPrimaryTypes() { if (requiredPrimaryTypeNames.length == 0) { // Per the JavaDoc, this method should never return null or an empty array; if there are no constraints, // then this method should include an array with 'nt:base' as the required primary type. JcrNodeType[] result = new JcrNodeType[1]; result[0] = nodeTypeManager.getNodeTypes().getNodeType(JcrNtLexicon.BASE); return result; } // Make a copy so that the caller can't modify our content ... JcrNodeType[] result = new JcrNodeType[requiredPrimaryTypeNames.length]; int i = 0; final NodeTypes nodeTypes = nodeTypeManager.getNodeTypes(); for (Name name : requiredPrimaryTypeNames) { result[i++] = nodeTypes.getNodeType(name); } return result; } /** * Returns the required primary type names for this object as specified in the constructor. This method is useful for callers * that wish to access this information while this node definition's parent node is being registered. * * @return the required primary type names */ Name[] requiredPrimaryTypeNames() { return this.requiredPrimaryTypeNames; } /** * Get the set of names of the primary types. * * @return the required primary type names */ Set<Name> requiredPrimaryTypeNameSet() { return requiredPrimaryTypeNameSet; } @Override public String[] getRequiredPrimaryTypeNames() { if (requiredPrimaryTypeNames == null) return new String[0]; String[] rptNames = new String[requiredPrimaryTypeNames.length]; for (int i = 0; i < requiredPrimaryTypeNames.length; i++) { rptNames[i] = string(requiredPrimaryTypeNames[i]); } return rptNames; } @Override public String getDefaultPrimaryTypeName() { return string(this.defaultPrimaryTypeName); } /** * Determine if this node definition will allow a child with the supplied primary type. This method checks this definition's * {@link #getRequiredPrimaryTypes()} against the supplied primary type and its supertypes. The supplied primary type for the * child must be or extend all of the types defined by the {@link #getRequiredPrimaryTypes() required primary types}. * * @param childPrimaryType the primary type of the child * @return true if the primary type of the child (or one of its supertypes) is one of the types required by this definition, * or false otherwise */ final boolean allowsChildWithType( JcrNodeType childPrimaryType ) { if (childPrimaryType == null) { // The definition must have a default primary type ... if (defaultPrimaryTypeName != null) { return true; } return false; } // The supplied primary type must be or extend all of the required primary types ... for (Name requiredPrimaryTypeName : requiredPrimaryTypeNameSet) { if (!childPrimaryType.isNodeType(requiredPrimaryTypeName)) return false; } return true; } /** * Creates a new <code>JcrNodeDefinition</code> that is identical to the current object, but with the given * <code>declaringNodeType</code>. Provided to support immutable pattern for this class. * * @param declaringNodeType the declaring node type for the new <code>JcrNodeDefinition</code> * @return a new <code>JcrNodeDefinition</code> that is identical to the current object, but with the given * <code>declaringNodeType</code>. */ JcrNodeDefinition with( JcrNodeType declaringNodeType ) { return new JcrNodeDefinition(this.context, declaringNodeType.nodeTypeManager(), declaringNodeType, key(), name, getOnParentVersion(), isAutoCreated(), isMandatory(), isProtected(), allowsSameNameSiblings(), defaultPrimaryTypeName, requiredPrimaryTypeNames); } JcrNodeDefinition with( ExecutionContext context ) { return new JcrNodeDefinition(context, this.nodeTypeManager, this.declaringNodeType, key(), name, getOnParentVersion(), isAutoCreated(), isMandatory(), isProtected(), allowsSameNameSiblings(), defaultPrimaryTypeName, requiredPrimaryTypeNames); } JcrNodeDefinition with( RepositoryNodeTypeManager nodeTypeManager ) { return new JcrNodeDefinition(this.context, nodeTypeManager, this.declaringNodeType, key(), name, getOnParentVersion(), isAutoCreated(), isMandatory(), isProtected(), allowsSameNameSiblings(), defaultPrimaryTypeName, requiredPrimaryTypeNames); } @Override public int hashCode() { return getId().toString().hashCode(); } @Override public boolean equals( Object obj ) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; JcrNodeDefinition other = (JcrNodeDefinition)obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; return true; } @Override public String toString() { ValueFactory<String> strings = context.getValueFactories().getStringFactory(); StringBuilder sb = new StringBuilder(); NodeDefinitionId id = getId(); sb.append(strings.create(id.getNodeTypeName())); sb.append('/'); sb.append(strings.create(id.getChildDefinitionName())); if (id.hasRequiredPrimaryTypes()) { sb.append(" (required primary types = ["); boolean first = true; for (Name requiredPrimaryType : id.getRequiredPrimaryTypes()) { if (first) first = false; else sb.append(','); sb.append(requiredPrimaryType.getString()); } sb.append("])"); } return sb.toString(); } }